> ## Documentation Index
> Fetch the complete documentation index at: https://tyk-docs-goja-inline-mintlify.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript Plugins

> Write, test and deploy JavaScript plugins for Tyk Gateway — the goja engine, hook types, the request/session/config objects, the JavaScript API, and inline, bundle and file-mount deployment.

## Overview

JavaScript plugins let you run custom logic directly inside Tyk Gateway, without an external process or a separate runtime. There are three components that can be scripted with JavaScript:

1. **Custom JavaScript plugins**: middleware that executes either *pre* or *post* request processing. A *pre* plugin runs before any session or token validation; a *post* plugin runs after the request has passed all checks and is ready to be proxied upstream.
2. **Dynamic event handlers**: components that fire on API events. They run asynchronously and are documented in [Gateway events](/api-management/gateway-events#set-up-a-webhook-event-handler-in-the-tyk-oas-api-definition). JavaScript event handlers currently run on the legacy otto (ES5.1) engine regardless of the API's plugin driver; the goja engine applies to custom plugins and virtual endpoints only.
3. **Virtual endpoints**: programmable middleware invoked towards the end of the request chain. Unlike custom plugins, a [virtual endpoint](/api-management/traffic-transformation/virtual-endpoints) *terminates* the request and returns its own response.

This page is the reference for the JavaScript runtime, the plugin contract, the objects and API available to your code, and how to deploy a plugin. To author and test a plugin end to end, use the [tyk-plugin-starter](https://github.com/TykTechnologies/tyk-plugin-starter) repository (see [Writing a plugin](#writing-a-plugin)).

<Note>
  **Each plugin runs in its own scope**

  On the goja engine, every custom-middleware plugin is compiled in an isolated scope and its handler is exposed under a unique internal alias. Plugins on the same API can therefore reuse the same handler name (such as `var handler`) without clobbering each other — across files, bundles and inline code alike. The legacy otto engine does not isolate plugins: there, all functions share one global namespace and the last one loaded wins, so distinct names are required.
</Note>

## The goja engine

Tyk Gateway runs JavaScript on [goja](https://github.com/dop251/goja), a pure-Go ECMAScript engine embedded in the gateway binary. goja's guaranteed language level is **ECMAScript 5.1**, which it implements in full. It also runs much of ES6 and later (`let`/`const`, arrow functions, template literals, destructuring, classes, `Promise`, `Map`/`Set`/`Symbol`, optional chaining `?.`, nullish coalescing `??`, `BigInt`), but that surface is a subset that grows with the gateway's bundled goja version, so treat those features as available rather than guaranteed. For maximum portability, keep your build target at ES5.1 and let your bundler down-level modern syntax to it. (Earlier gateway releases ran JavaScript on [otto](https://github.com/robertkrimen/otto), which is limited to ES5.1 and remains selectable for backward compatibility; see [Selecting the engine](#selecting-the-engine).)

<Note>
  **Availability**

  The goja engine was released in Tyk Gateway v5.14. On earlier versions, JavaScript runs on the otto engine only.
</Note>

Two properties of the runtime are worth knowing up front:

* **One virtual machine per API.** Each managed API gets its own isolated VM, so plugins on different APIs cannot share globals or call each other's functions. Cross-API data sharing is only possible through the [session object](#the-session-object) when a key is shared across APIs.
* **A fresh runtime per request.** Each request executes on a new runtime, so global state does not leak between concurrent requests. Treat the VM as stateless between invocations; persist anything durable through `session.meta_data` or the [key session API](#working-with-the-key-session-object).

goja is **not** Node.js. There is no `require`/`import`, no `async`/`await`, no `setTimeout`, and no Node standard library. For the full list of runtime constraints, available globals and the libraries that bundle cleanly, see [`AGENTS.md`](https://github.com/TykTechnologies/tyk-plugin-starter/blob/main/AGENTS.md) in the starter repo.

### Enabling the JSVM

The JavaScript Virtual Machine (JSVM) is disabled by default. Enable it by setting [`enable_jsvm`](/tyk-oss-gateway/configuration#enable_jsvm) to `true` in `tyk.conf`. This single flag enables JavaScript for custom middleware, event handlers and virtual endpoints alike.

### Selecting the engine

The engine is chosen per API by the plugin **driver**:

* `javascript` selects the goja engine. In OAS this is `x-tyk-api-gateway.middleware.global.pluginConfig.driver`; in a Tyk Classic API definition it is `custom_middleware.driver`.
* `otto` selects the legacy engine, retained for backward compatibility.

The same driver flag governs [virtual endpoints](#virtual-endpoints-and-the-driver): a virtual endpoint runs on goja only when its API sets the driver to `javascript`. With no driver set, virtual endpoints default to the legacy otto engine, so set `javascript` explicitly to run them on goja.

### Migrating from otto

goja is API-definition compatible with otto — the same hooks, the same `request`/`session`/`config` objects and the same `TykJS` middleware contract — so most plugins run unchanged on the `javascript` driver. One difference catches plugins that relied on it:

**The `underscore` library is no longer a global.** The otto engine auto-loaded [underscore.js](https://underscorejs.org) and exposed it as `_`. goja does not bundle it, so a plugin that calls `_.map`, `_.template` and similar will fail. Most underscore helpers have native equivalents in goja's fuller ES5.1+/ES6 runtime (`Array.prototype.map`/`filter`/`reduce`, `Object.keys`/`values`/`entries`); where you need more, bundle a utility library such as `lodash` into your `plugin.js` with the [starter](https://github.com/TykTechnologies/tyk-plugin-starter) rather than relying on a gateway-provided global.

## Hook types

A JavaScript plugin attaches to one point in the request lifecycle. The hook you choose determines when it runs and what it can do:

| Hook                | Tyk Classic field                 | OAS field                      | Runs                                                       |
| ------------------- | --------------------------------- | ------------------------------ | ---------------------------------------------------------- |
| Pre                 | `custom_middleware.pre`           | `prePlugins`                   | Before authentication — no session available yet           |
| Authentication      | `custom_middleware.auth_check`    | `server.authentication.custom` | Replaces Tyk's built-in authentication                     |
| Post-authentication | `custom_middleware.post_key_auth` | `postAuthenticationPlugins`    | After authentication, before other checks                  |
| Post                | `custom_middleware.post`          | `postPlugins`                  | After all checks, just before proxying upstream            |
| Response            | `custom_middleware.response`      | `responsePlugins`              | After the upstream responds, before the client receives it |

Each plugin entry attaches to exactly one hook. For a fuller treatment of the request chain and what each stage can do, see [Plugin types](/api-management/plugins/plugin-types). Virtual endpoints are documented separately under [Virtual endpoints](/api-management/traffic-transformation/virtual-endpoints).

## Writing a plugin

The [tyk-plugin-starter](https://github.com/TykTechnologies/tyk-plugin-starter) repository is the supported way to author JavaScript plugins. It is designed for both humans and AI agents and provides:

* A TypeScript project with the published [`@tyk-technologies/tyk-plugin-types`](https://www.npmjs.com/package/@tyk-technologies/tyk-plugin-types) typings, so the request/session/config objects and the Tyk globals autocomplete.
* A **local test harness** that mocks the goja runtime, so you can unit-test a plugin in plain Node without running a gateway.
* A webpack build that emits a single `plugin.js`, and a script that packages it into a deployable bundle.
* Six runnable, tested examples covering every hook type (pre, auth\_check, post\_key\_auth, post, response).

The typical loop is:

```bash theme={null}
npx degit TykTechnologies/tyk-plugin-starter my-plugin
cd my-plugin
npm install
# edit src/plugin.ts
npm test          # unit-test against the mock JSVM
npm run build     # emit dist/plugin.js
```

Once you have a built `plugin.js`, deploy it using one of the methods in [Deploying JavaScript plugins](#deploying-javascript-plugins).

## The plugin contract

### Creating a middleware component

Tyk injects a `TykJS` namespace into the VM. You create a middleware object with the `NewMiddleware({})` constructor and register your function with `NewProcessRequest()`. This closure receives the three Tyk data objects — [`request`](#the-request-object), [`session`](#the-session-object) and [`config`](#the-config-object):

```js theme={null}
var handler = new TykJS.TykMiddleware.NewMiddleware({});

handler.NewProcessRequest(function (request, session, config) {
  // ... inspect or modify the request ...
  return handler.ReturnData(request, session.meta_data);
});

// Required when building with a bundler: expose the handler as a global.
globalThis.handler = handler;
```

<Note>
  **Two rules to remember**

  * For custom plugins and event handlers loaded from a file, the source filename must match the function name. Virtual endpoints have no such restriction.
  * If you build with a bundler (such as the webpack setup in the starter), the bundler scopes your `var handler` locally, so the gateway cannot see it. Assign `globalThis.handler = handler` to expose it. The starter's test harness asserts this for you.
</Note>

### Returning from a plugin

How you return depends on the plugin type.

**Custom plugins** modify the `request` and `session.meta_data` objects, but those changes are applied by the gateway *after* the function returns — so you must pass them back. Omitting the return causes the plugin to fail:

```js theme={null}
return handler.ReturnData(request, session.meta_data);
```

A custom plugin normally passes the request on to the next middleware. It can instead **terminate** the request and return a custom response to the client by setting `request.ReturnOverrides` — see [Using ReturnOverrides](#using-returnoverrides).

**Virtual endpoints** always terminate the request, so they return differently. Build a response object and pass it, with the session metadata, to `TykJsResponse`:

```js theme={null}
return TykJsResponse(responseObject, session.meta_data);
```

The `responseObject` has the structure:

* `Code`: integer HTTP status code
* `Headers`: object of header key/value pairs
* `Body`: response body string (plain text, JSON or XML)

See the [virtual endpoint examples](/api-management/traffic-transformation/virtual-endpoints#examples) for complete handlers.

## The request, session and config objects

Your function receives three Tyk data objects. This section covers their behaviour and the fields you'll use most; for the full struct shapes, see the [data structures in Rich Plugins](/api-management/plugins/rich-plugins#rich-plugins-data-structures).

### The `request` object

The `request` object exposes the API request as a set of fields you can read and modify. Changes take effect as the request continues through the middleware chain. (Virtual endpoints receive a [different request shape](#the-virtual-endpoint-request-object).)

```typescript theme={null}
class ReturnOverrides {
  ResponseCode: number = 200;
  ResponseBody: string = "";
  ResponseHeaders: { [key: string]: string } = {};
}

class Request {
  Headers: { [key: string]: string[] } = {};
  SetHeaders: { [key: string]: string } = {};
  DeleteHeaders: string[] = [];
  Body: string = "";
  URL: string = "";
  AddParams: { [key: string]: string } = {};
  DeleteParams: string[] = [];
  ReturnOverrides: ReturnOverrides = new ReturnOverrides();
  IgnoreBody: boolean = false;
  Method: string = "";
  RequestURI: string = "";
  Scheme: string = "";
}
```

* `Headers`: current request headers, as string arrays. Read-only; modify headers through `SetHeaders`/`DeleteHeaders`.
* `SetHeaders`: headers to set when the function returns. Existing headers are overwritten; new ones are added.
* `DeleteHeaders`: header names to delete from the outgoing request. `DeleteHeaders` is applied before `SetHeaders`.
* `Body`: the request body. Assigning to it overwrites the body.
* `URL`: the path portion of the outbound URL. Modify it to route to a different upstream path.
* `AddParams`: query parameters to add to the request.
* `DeleteParams`: query parameters to remove. `DeleteParams` is applied before `AddParams`.
* `ReturnOverrides`: set these to terminate the request and return a response (see below).
* `IgnoreBody`: if `true`, the original request body is used; if `false` (default) the `Body` field is used.
* `Method`: the HTTP method (`GET`, `POST`, …).
* `RequestURI`: the request URI including query string, e.g. `/path?key=value`.
* `Scheme`: the URL scheme, e.g. `http` or `https`.

#### Using `ReturnOverrides`

Set values in `request.ReturnOverrides` to terminate the request and respond to the client directly — the request is not proxied upstream. In this example, when a condition is met the gateway returns `HTTP 403` with a custom header:

```js theme={null}
var handler = new TykJS.TykMiddleware.NewMiddleware({});

handler.NewProcessRequest(function (request, session, config) {
  if (someCondition) {
    request.ReturnOverrides.ResponseCode = 403;
    request.ReturnOverrides.ResponseBody = "Access Denied";
    request.ReturnOverrides.ResponseHeaders = { "X-Error": "the-condition" };
  }
  return handler.ReturnData(request, session.meta_data);
});
```

#### The virtual endpoint `request` object

[Virtual endpoint](/api-management/traffic-transformation/virtual-endpoints) functions receive this request shape instead:

```typescript theme={null}
class VirtualEndpointRequest {
  Body: string = "";
  Headers: { [key: string]: string[] } = {};
  Params: { [key: string]: string[] } = {};
  Scheme: string = "";
  URL: string = "";
}
```

* `Body`: HTTP request body.
* `Headers`: request headers, e.g. `"Accept": ["*/*"]`.
* `Params`: decoded query and form parameters, e.g. `{ "userId": ["123"] }`.
* `Scheme`: URL scheme (`http` or `https`).
* `URL`: full request URL, e.g. `/vendpoint/anything?user_id=123&confirm=true`.

<Note>
  **Note**

  Each query and form parameter is stored as an array. Repeated assignments append to the array — a request to `/vendpoint/anything?user_id[]=123&user_id[]=234` produces `Params: { "user_id[]": ["123", "234"] }`.
</Note>

### The `session` object

Tyk uses an internal [session object](/api-management/policies#what-is-a-session-object) to track quota, rate limits, access rights and auth data for a key. A plugin can read the session, but — other than `meta_data` — it cannot be modified directly, because the gateway relies on it. Deserialising the session into the VM is relatively expensive, so it can be disabled where it isn't needed.

**Availability**

* Pre-stage plugins have **no** session object — authentication hasn't run yet.
* Virtual endpoints receive the session only if it is enabled in the middleware configuration.

**Sharing data between plugins**

The `meta_data` key/value field is written back to the session store, so later middleware can read what an earlier plugin wrote. This data is persistent and is also accessible through the `/tyk/keys/` REST API. Because each API has its own VM, sharing data *across* APIs is only possible through the session object when a key is shared between them.

### The `config` object

The read-only `config` object carries data from the API definition:

* `APIID`: the API's unique identifier
* `OrgID`: the organization identifier
* `config_data`: custom attributes from the API definition

**Adding custom attributes.** For Tyk OAS APIs, add them under `x-tyk-api-gateway.middleware.global.pluginConfig.data`:

```json theme={null}
{
  "x-tyk-api-gateway": {
    "middleware": {
      "global": {
        "pluginConfig": {
          "data": {
            "enabled": true,
            "value": { "foo": "bar" }
          }
        }
      }
    }
  }
}
```

For Tyk Classic APIs, add them under `config_data` at the root of the API definition:

```json theme={null}
{
  "config_data": {
    "foo": "bar"
  }
}
```

## The JavaScript API

The gateway adds a set of global functions to the VM, giving your script the ability to log, make outbound HTTP requests and read or write key session data:

* `log(string)`: logs the string to the gateway's logger as an INFO line prefixed with `JSVM Log:`. Useful for debugging.
* `rawlog(string)`: logs the string with no prefix or timestamp, for custom log formats.
* `b64enc(string)` / `b64dec(string)`: standard base64 encode/decode.
* `rawb64enc(string)` / `rawb64dec(string)`: raw (unpadded) base64 encode/decode.
* `TykMakeHttpRequest(JSON.stringify(requestObject))`: make an outbound HTTP request (see below).
* `TykBatchRequest(...)`: like `TykMakeHttpRequest` but uses Tyk's [batch request feature](/api-management/batch-processing).
* `TykGetKeyData` / `TykSetKeyData`: read and write key session data (see [Working with the key session object](#working-with-the-key-session-object)).

### Making HTTP requests

`TykMakeHttpRequest` encodes the request as JSON, which the gateway deserialises in the main binary and translates into a system HTTP call. The request object has the structure:

```js theme={null}
var newRequest = {
  "Method": "POST",
  "Body": JSON.stringify(event),
  "Headers": {},
  "Domain": "http://foo.com",
  "Resource": "/event/quotas",
  "FormData": { "field": "value" }
};
```

<Note>
  **Note**

  To include query-string values, append them to the `Domain` property.
</Note>

Tyk returns a simplified, JSON-encoded response that you decode before use:

```js theme={null}
var usableResponse = JSON.parse(response);
log("Response code: " + usableResponse.Code);
log("Response body: " + usableResponse.Body);
```

The response shape is `{ Code: int, Body: string, Headers: map[string][]string }`. The call is synchronous — execution blocks until the response is received.

### Working with the key session object

Two functions read and write the [session object](/api-management/policies#what-is-a-session-object) for a key:

* `TykGetKeyData(key, apiID)`: retrieve a session object.

  ```js theme={null}
  // In an event handler: key from the event, API ID from the context variable.
  var thisSession = JSON.parse(TykGetKeyData(event.EventMetaData.Key, context.APIID));
  log("Expires: " + thisSession.expires);
  ```

* `TykSetKeyData(key, value, ...)`: write data back into the session store.

  ```js theme={null}
  thisSession.expires = thisSession.expires + 1000;
  TykSetKeyData(event.EventMetaData.Key, JSON.stringify(thisSession));
  ```

### Storage Operations

JavaScript plugins can persist small key-value data in the storage (Redis) used by Tyk Gateway using the `TykStorage` functions. This bounded key-value store lets plugins share state across requests and across gateway nodes that use the same storage, supporting patterns such as idempotency guards, distributed locks, replay protection and simple counters.

<Note>
  The storage functions are available from Tyk Gateway v5.15.0 and only in the goja JSVM engine. They are not available in the legacy otto engine.
</Note>

The following functions are provided:

* `TykStorageGet(key)`: retrieves the value stored at `key`, returned as a string; returns `null` if the key does not exist
* `TykStorageSet(key, value, ttlSeconds)`: stores `value` at `key` with an expiry of `ttlSeconds`; pass `0` for no expiry
* `TykStorageSetNX(key, value, ttlSeconds)`: atomically stores `value` at `key` only if the key does not already exist (Redis `SET NX EX`); returns `true` if the key was claimed, `false` if it already existed; this is the primitive to use for idempotency guards, locks and replay protection
* `TykStorageDel(key)`: deletes `key` from storage
* `TykStorageTTL(key)`: returns the remaining time to live of `key` in seconds; returns `-1` if the key has no expiry and `-2` if the key does not exist
* `TykStorageIncr(key, ttlSeconds)`: atomically increments the integer stored at `key` and returns the new value as a string (a string is returned because JavaScript numbers lose precision above 2^53); `ttlSeconds` is applied only when the increment creates the key

In this example, `TykStorageSetNX` is used to implement an idempotency guard: the first request carrying a given idempotency key claims it and is passed through to the upstream, while any repeat request within 24 hours is rejected with `HTTP 409 Conflict`:

```js theme={null}
var idempotencyGuard = new TykJS.TykMiddleware.NewMiddleware({});

idempotencyGuard.NewProcessRequest(function(request, session, config) {
    var idempotencyKey = request.Headers["Idempotency-Key"];

    if (idempotencyKey) {
        try {
            // Claim the key for 24 hours; only the first request succeeds
            var claimed = TykStorageSetNX("idem:" + idempotencyKey[0], "1", 86400);

            if (!claimed) {
                request.ReturnOverrides.ResponseCode = 409;
                request.ReturnOverrides.ResponseBody = "Duplicate request";
            }
        } catch (e) {
            // Storage unavailable; fail closed rather than risk a duplicate
            log("storage error: " + e);
            request.ReturnOverrides.ResponseCode = 503;
            request.ReturnOverrides.ResponseBody = "Try again later";
        }
    }

    return idempotencyGuard.ReturnData(request, session.meta_data);
});
```

<Note>
  All storage functions throw a JavaScript exception on failure, for example if the storage is unreachable, the operation exceeds the 2 second timeout, the key exceeds 256 bytes or the value exceeds 64 KB. They never fail silently by returning `false` or `undefined`, so wrap calls in `try/catch` and decide explicitly how your plugin should behave when storage is unavailable. Keys are automatically placed under a dedicated namespace (`jsvm-store:`) that is enforced by the gateway, so plugins cannot read or modify keys used internally by Tyk.
</Note>

<Warning>
  Atomicity guarantees, such as those provided by `TykStorageSetNX` and `TykStorageIncr`, apply per Redis instance. In deployments where gateway nodes use separate Redis instances, which is common in Hybrid deployments, guarantees such as idempotency and replay protection hold per node rather than globally. For a global guarantee, all gateways must share a single Redis deployment. This is the same consistency model as Tyk's rate limiting.
</Warning>

For worked examples of these and the other globals, see the [examples in the starter repo](https://github.com/TykTechnologies/tyk-plugin-starter/tree/main/examples).

## Deploying JavaScript plugins

A built `plugin.js` reaches the gateway one of three ways. They differ in **how the plugin artifact is distributed to your gateway nodes** — not in capability or environment. All three are production-grade, and all require the [JSVM to be enabled](#enabling-the-jsvm) (`enable_jsvm: true`).

| Method                        | What it is                                                  | How it reaches the node                                                                                                                      |
| ----------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| [Inline `code`](#inline-code) | JS source base64-encoded into the API definition            | Travels with the API definition — no separate artifact. Suits GitOps with `tyk-sync` or pushing through the API.                             |
| [Bundle](#bundles)            | A zip (`plugin.js` + `manifest.json`) on a bundle server    | Downloaded at runtime over HTTP. Supports signing and sharing one plugin across many APIs.                                                   |
| [File mount](#file-mount)     | A `.js` file referenced by `path` on the gateway filesystem | You place the file on each node — baked into the image, mounted as a volume, or shipped by config management (the same model as Go plugins). |

Inline `code` and file `path` can be **combined on a single API** — they're loaded by independent passes, so some hooks can be inline while others are file-mounted. A **bundle** is the exception: it replaces an API's entire middleware section, so it can't be mixed with `code` or `path` entries (see [Bundles](#bundles)).

### Inline code

Inline code embeds the plugin source directly in the API definition, so a single document carries both the API configuration and its custom logic — no bundle server and no separate artifact. The source is supplied base64-encoded in the `code` field of a plugin entry, and the plugin driver is set to `javascript`.

The following minimal Tyk OAS document attaches a pre-plugin that renames a header:

```yaml theme={null}
openapi: 3.0.3
info:
  title: header-rename
  version: 1.0.0
paths: {}
x-tyk-api-gateway:
  info:
    name: header-rename
    state:
      active: true
  upstream:
    url: http://httpbin.org/
  server:
    listenPath:
      value: /header-rename/
      strip: true
    authentication:
      enabled: false
  middleware:
    global:
      pluginConfig:
        driver: javascript
      prePlugins:
        - enabled: true
          functionName: handler
          code: dmFyIGhhbmRsZXIgPSBuZXcgVHlrSlMuVHlrTWlkZGxld2FyZS5OZXdNaWRkbGV3YXJlKHt9KTsKaGFuZGxlci5OZXdQcm9jZXNzUmVxdWVzdChmdW5jdGlvbiAocmVxdWVzdCwgc2Vzc2lvbiwgY29uZmlnKSB7CiAgdmFyIHYgPSByZXF1ZXN0LkhlYWRlcnNbJ1gtRm9vJ107CiAgaWYgKHYgJiYgdi5sZW5ndGggPiAwKSB7CiAgICByZXF1ZXN0LlNldEhlYWRlcnNbJ1gtQmFyJ10gPSB2WzBdOwogICAgcmVxdWVzdC5EZWxldGVIZWFkZXJzLnB1c2goJ1gtRm9vJyk7CiAgfQogIHJldHVybiBoYW5kbGVyLlJldHVybkRhdGEocmVxdWVzdCwge30pOwp9KTsKZ2xvYmFsVGhpcy5oYW5kbGVyID0gaGFuZGxlcjsK
          rawBodyOnly: false
          requireSession: false
```

The `code` value above decodes to the plugin source, which renames the `X-Foo` header to `X-Bar`:

```js theme={null}
var handler = new TykJS.TykMiddleware.NewMiddleware({});
handler.NewProcessRequest(function (request, session, config) {
  var v = request.Headers['X-Foo'];
  if (v && v.length > 0) {
    request.SetHeaders['X-Bar'] = v[0];
    request.DeleteHeaders.push('X-Foo');
  }
  return handler.ReturnData(request, {});
});
globalThis.handler = handler;
```

Each entry under `prePlugins` / `postAuthenticationPlugins` / `postPlugins` / `responsePlugins` accepts:

| Field            | Type   | Notes                                                                 |
| ---------------- | ------ | --------------------------------------------------------------------- |
| `enabled`        | bool   | Required. Set `false` to disable without removing the entry.          |
| `functionName`   | string | The handler identifier (e.g. `handler`).                              |
| `code`           | string | Base64-encoded JS source. When set, `path` is ignored for this entry. |
| `path`           | string | Path to a JS file. Mutually exclusive with `code`.                    |
| `requireSession` | bool   | Make the session available to the hook.                               |
| `rawBodyOnly`    | bool   | Skip request-body decoding (useful for large or binary payloads).     |

The `code` field must be base64; the gateway decodes it unconditionally, so raw JavaScript in this field will fail to load. Produce the value from your built plugin with:

```bash theme={null}
base64 < dist/plugin.js | tr -d '\n'
```

Because inline plugins live in the API definition, updating one is an ordinary API update — `PUT` the document and reload. The gateway re-decodes and re-compiles the `code` field on reload; there is no on-disk cache to invalidate:

```bash theme={null}
curl -X PUT \
  -H "x-tyk-authorization: $TYK_SECRET" \
  -H "Content-Type: application/yaml" \
  --data-binary @api.yaml \
  http://gateway:8080/tyk/apis/oas/header-rename
curl -H "x-tyk-authorization: $TYK_SECRET" http://gateway:8080/tyk/reload/group
```

<Note>
  **The source is visible**

  Inline `code` is plain text in the API definition to anyone with read access. Don't embed secrets, and remember to rebuild before re-encoding — the gateway will happily run stale code.
</Note>

### Bundles

A bundle packages `plugin.js` with a `manifest.json` (declaring the driver and hook entries) into a zip that the gateway downloads from a bundle server. Bundles are the right choice when one plugin is shared across many APIs or when you want to version plugin artifacts independently of API definitions. The starter repo's `npm run build:bundle` produces a valid bundle without a gateway binary.

An API references its bundle through `custom_middleware_bundle` (Classic) and requires the bundle downloader to be enabled on the gateway. That single field also accepts a **comma-separated list** of bundle names — for example `"auth.zip,logging.zip"` — which fetches each and merges their manifests in declaration order.

<Note>
  **Bundles replace inline and file-mounted middleware**

  When an API references a bundle, the bundle's manifest **replaces** the API's `custom_middleware` section entirely — any inline `code` or file `path` entries on the definition are ignored. Ship all of an API's middleware in the bundle, or define it on the API with `code`/`path` and leave the bundle field unset.
</Note>

### File mount

Reference a `.js` file from the API definition with the `path` field. This is a first-class alternative to inline `code` — the only difference is that you distribute the file to your gateway nodes yourself, the same way you would a Go plugin: bake it into the gateway image, mount it as a volume, or ship it with your configuration management. Every node that serves the API must have the file at the referenced path.

**By path in the API definition.** Register the plugin in the `custom_middleware` block, pointing `path` at the file. The path is resolved relative to the gateway's working directory (or given as an absolute path):

```json theme={null}
"custom_middleware": {
  "driver": "javascript",
  "pre": [
    {
      "name": "sampleMiddleware",
      "path": "middleware/sample.js",
      "require_session": false
    }
  ],
  "post": [
    {
      "name": "sampleMiddleware",
      "path": "middleware/sample.js",
      "require_session": false
    }
  ]
}
```

* `pre` / `post`: lists of plugins run in order, before authentication (`pre`) or after all checks and before proxying upstream (`post`).
* `name`: the handler name. Case-sensitive; must match the name your script registers with `NewMiddleware`.
* `path`: the JS file, loaded into the VM when the API initialises. Reloading the API hot-swaps the plugin with no interruption.
* `require_session`: irrelevant for `pre` (no session yet); for `post`, set `true` to receive the `session` object.

**By directory structure (auto-loader).** As a convenience — useful when you can't edit the API definition directly, for example a Dashboard-managed Self-Managed or Hybrid deployment — Tyk can auto-load plugins from a directory named after the API ID, with no `path` reference in the API definition:

```text theme={null}
middleware
  / {API Id}
    / pre
      / {middlewareObjectName}.js
    / post
      / {middlewareObjectName}_with_session.js
```

Tyk matches a folder to the API ID being loaded and loads the `pre` and `post` plugins from the respective directories. The filename must match the registered object name exactly. Append `_with_session` to a `post` plugin's filename to inject the session object.

### Virtual endpoints and the driver

Virtual endpoints honour the same `driver` flag as custom middleware. A virtual endpoint runs on goja when its API sets the driver to `javascript`; with no driver set it runs on the legacy otto engine. Set the driver explicitly to move virtual endpoints onto goja. See [Virtual endpoints](/api-management/traffic-transformation/virtual-endpoints) for configuration.

## See also

* [tyk-plugin-starter](https://github.com/TykTechnologies/tyk-plugin-starter) — author, test and build JavaScript plugins, with runnable examples
* [Plugin types](/api-management/plugins/plugin-types) — hook stages and capabilities
* [Rich plugins](/api-management/plugins/rich-plugins) — plugin data structures and bundles
* [Virtual endpoints](/api-management/traffic-transformation/virtual-endpoints) — programmable terminating middleware
* [Go plugins](/api-management/plugins/golang) — native plugins for higher performance
