Skip to content

Addons (Script Engine)

Addons let you write JavaScript scripts that process every HTTP request and response passing through Ghost. Think of them as programmable rules — you write a short script that runs automatically on every flow, and it can tag flows, modify headers, block requests, generate synthetic responses, or log information to a console.

For example: you could write a 5-line addon that tags every request to your payment API with “payment” so you can easily filter them later. Or an addon that adds a missing security header to every response so you can test what your app would look like with proper security headers. Or one that blocks all requests to analytics domains so they don’t clutter your traffic list.

Addons run in sandboxed JavaScript VMs powered by goja, a pure Go JavaScript engine. This means addons are fast, safe, and can’t crash Ghost — even if your script has a bug, it’s isolated in its own sandbox.

When a request arrives at Ghost’s proxy, it passes through the interceptor pipeline — a chain of processing stages. The addon engine is one stage in this chain. For each flow, Ghost runs every enabled addon in priority order, giving each addon a chance to inspect and modify the request before it’s sent upstream, and then inspect and modify the response before it’s delivered back to the client.

What this diagram shows:

  1. A flow arrives at the proxy and enters the interceptor pipeline
  2. The addon engine’s OnRequest handler is called — it loops through every enabled addon (sorted by priority number, lowest first) and runs each addon’s onRequest callback in its own sandboxed VM
  3. Each addon can inspect the request and take an action: continue (do nothing), drop (block the request), respond (return a custom response without contacting the server), or modify (change headers, body, etc.)
  4. After the response comes back from the upstream server, the same process happens with onResponse — each addon gets a chance to inspect and modify the response
  5. Each addon runs in its own goja VM with its own state, so addons can’t interfere with each other

Here’s a complete addon that tags API errors and logs a warning:

// Register a handler that runs when a RESPONSE comes back
ghost.onResponse(function(flow) {
// Check if this is a server error (status 500+)
if (flow.response.status_code >= 500) {
// Add a tag so we can filter for these later
flow.tag("server-error");
// Log a warning to the addon console
ghost.warn("Server error on " + flow.request.url);
}
// Check the response body for debug info that shouldn't be in production
var body = flow.response.body;
if (body && body.indexOf("stack trace") !== -1) {
flow.tag("leaked-stacktrace");
ghost.error("Stack trace leaked in response!");
}
});

And here’s one that adds a custom header to every request:

ghost.onRequest(function(flow) {
// Add a debug header to all requests
flow.request.setHeader("X-Debug-Mode", "true");
// Tag requests to specific hosts
if (flow.request.host === "api.hepsiburada.com") {
flow.tag("hepsi-api");
}
});

Every handler receives a flow object with full access to the request and response data.

Top-level properties:

PropertyTypeDescription
flow.idstringThe unique flow ID (ULID)
flow.session_idstringWhich session this flow belongs to
flow.sourcestringWhere the flow came from: proxy, replay, script, or import

Request properties (flow.request):

PropertyTypeDescription
flow.request.methodstringHTTP method: GET, POST, PUT, DELETE, etc.
flow.request.urlstringThe full request URL including query parameters
flow.request.hoststringThe hostname (e.g., api.example.com)
flow.request.pathstringThe URL path (e.g., /api/v1/users/123)
flow.request.protostringThe HTTP protocol version (e.g., HTTP/1.1, HTTP/2)
flow.request.headersobjectRequest headers as a key-value map (single value per key). Read-only — use setHeader/removeHeader to modify.
flow.request.content_typestringThe Content-Type header value
flow.request.bodystringThe request body as a string

Request mutation methods:

MethodDescription
flow.request.setHeader(name, value)Set or replace a request header
flow.request.removeHeader(name)Delete a request header
flow.request.setBody(str)Replace the entire request body

Response properties (flow.response):

PropertyTypeDescription
flow.response.status_codenumberHTTP status code (200, 404, 500, etc.)
flow.response.status_textstringStatus text (e.g., “OK”, “Not Found”)
flow.response.headersobjectResponse headers as a key-value map. Read-only — use setHeader/removeHeader to modify.
flow.response.content_typestringThe Content-Type header value
flow.response.bodystringThe response body as a string

Response mutation methods:

MethodDescription
flow.response.setHeader(name, value)Set or replace a response header
flow.response.removeHeader(name)Delete a response header
flow.response.setBody(str)Replace the entire response body

Important: Response properties are only populated during onResponse handlers. In onRequest handlers, flow.response exists but all values are empty/zero and mutation methods are no-ops.

MethodDescription
flow.tag(name)Add a string tag to this flow. Tags appear in the traffic list and are searchable via GQL (tag:name). Tagged flows are also exempt from noise detection suppression.
flow.removeTag(name)Remove a tag from this flow
flow.annotate(key, value)Add a key-value annotation (metadata) to the flow
flow.drop()Drop the flow entirely. The request is NOT forwarded to the upstream server — the client receives no response. Use with caution. Only works in onRequest.
flow.respond(statusCode, headers, body)Return a custom response without contacting the upstream server. The headers parameter is a key-value object. Only works in onRequest — calling this in onResponse does nothing.
Property / MethodDescription
ghost.nameThe name of this addon (read-only string)
ghost.idThe ID of this addon (read-only string)
ghost.onRequest(fn)Register a function to be called when a request arrives. The function receives the flow object.
ghost.onResponse(fn)Register a function to be called when a response is received. The function receives the flow object.
ghost.log(msg)Write an info-level message to the addon’s log console
ghost.warn(msg)Write a warning-level message (displayed in amber)
ghost.error(msg)Write an error-level message (displayed in red)
ghost.storeKey-value store for persisting data between handler invocations (see below)

Each addon has its own private key-value store that persists across handler invocations — meaning you can save a value in one request and read it in the next. This is useful for tracking state, counting occurrences, or caching decisions.

MethodDescription
ghost.store.get(key)Returns the stored value, or undefined if the key doesn’t exist
ghost.store.set(key, value)Stores a value under the given key. The value can be any JavaScript type (string, number, object, array, boolean).
ghost.store.delete(key)Removes a key from the store
ghost.store.keys()Returns an array of all stored key names

Limits:

  • Maximum 1,024 keys per addon. If the store is full and you try to add a new key, the set is silently ignored and a warning is logged.
  • The store is in-memory only — data is lost when the addon is reloaded (saved) or Ghost restarts. This is by design: the store is for temporary runtime state, not permanent storage.
  • No per-value size limit.

Example — counting API errors:

ghost.onResponse(function(flow) {
if (flow.response.status_code >= 500) {
var count = ghost.store.get("error_count") || 0;
ghost.store.set("error_count", count + 1);
ghost.log("Total server errors so far: " + (count + 1));
}
});

Each addon runs in an isolated goja VM with strict safety limits to prevent runaway scripts from affecting Ghost’s performance:

LimitValueWhat Happens If Exceeded
Execution timeout5 seconds per handler invocationThe VM is interrupted via vm.Interrupt(). The handler is aborted and Ghost continues processing the next addon.
Call stack depth512 framesDeeply recursive functions are stopped to prevent stack overflow
Blocked globalsrequire, process, globalThisThese are removed from the VM environment — attempting to use them returns undefined. This prevents addons from accessing Node.js APIs or the global scope.

Important clarifications:

  • There is no explicit memory limit. The goja VM does not support memory caps. However, the 5-second timeout effectively limits how much memory a script can allocate.
  • If a script panics (throws an unrecoverable error), Ghost catches it with recover() — the addon is silently skipped and processing continues. Ghost never crashes due to an addon bug.
  • Each addon has its own sync.Mutex ensuring thread safety — only one handler can execute per addon at a time, even if multiple flows arrive simultaneously.

Click the “New” button in the Addons panel header to create a blank addon. Or click the dropdown arrow next to it to choose from 27 built-in templates organized by category (see Templates below).

The addon editor provides:

  • Editable name at the top (inline text input)
  • Priority number — lower number = higher priority = runs first. When multiple addons are enabled, they execute in priority order.
  • Code editor — a monospace text area for writing JavaScript. Tab key inserts 2-space indentation.
  • Save button with keyboard shortcut Ctrl+S / Cmd+S. An “Unsaved” badge appears when you have unsaved changes.
  • API Reference toggle — opens a side panel showing all available API methods organized by section (Lifecycle, Logging, Flow Actions, Request, Response, Flow Properties). Helpful when you forget a property name.
  • Delete button — requires a double-click within 3 seconds to confirm (prevents accidental deletion)

Each addon has a status toggle. Disabled addons are completely skipped during flow processing — they don’t consume any resources. The status is shown as a colored dot: green for active, grey for disabled.

When you save an addon (Ctrl+S or click Save), Ghost immediately:

  1. Unloads the old version (removes the old VM)
  2. Creates a fresh goja VM
  3. Compiles and runs the new code
  4. Re-registers the addon in the execution pipeline

This happens instantly — no proxy restart needed. However, be aware that the in-memory store is cleared on reload (a new VM means a fresh state). The priority-sorted execution order is automatically recalculated.

If the new code fails to compile, the addon is left unloaded (the old version was already removed). A warning is logged but the save still succeeds in the database — you can fix the code and save again.

Ghost ships with 27 addon templates across 5 categories. Each template is a working script you can use as-is or customize:

TemplateWhat It Does
Request LoggerLogs every request’s method, URL, and status to the addon console
JSON API MonitorLogs JSON API calls with response size and duration
Redirect TrackerLogs HTTP redirect chains (301, 302, 307, 308 responses)
Cookie InspectorLogs Set-Cookie headers to track cookie lifecycle
TemplateWhat It Does
Auto-Tag by Content TypeTags flows based on response content type (json, html, image, etc.)
API Error HighlighterTags all 4xx and 5xx responses with error-type tags
GraphQL Operation TaggerParses GraphQL request bodies and tags with the operation name
Large Payload FlaggerTags responses exceeding a configurable size threshold
TemplateWhat It Does
Block Tracker DomainsDrops requests to known advertising and tracking domains
Add Security HeadersAdds missing security headers (CSP, HSTS, X-Frame-Options) to responses
Strip Fingerprint HeadersRemoves headers that reveal server technology (Server, X-Powered-By)
Sensitive Data ScannerScans response bodies for patterns that look like exposed credentials or PII
CORS Misconfiguration DetectorFlags responses with permissive or misconfigured CORS headers
Missing Security HeadersTags responses missing recommended security headers
SQL Injection Pattern DetectorTags responses containing SQL error messages (potential SQLi indicators)
Verbose Error DetectorTags responses containing stack traces, debug info, or internal paths
Open Redirect DetectorTags redirect responses pointing to external domains
Mixed Content DetectorTags HTTPS pages that load resources over HTTP
TemplateWhat It Does
Mock API EndpointIntercepts requests to a specific URL and returns a mock response (using flow.respond())
CORS UnblockerAdds permissive CORS headers to all responses (useful for local development)
Auth Token InjectorAdds a configurable Authorization header to all requests to a target host
Contract ValidatorValidates response JSON against an expected schema structure
Performance BudgetTags flows exceeding configurable response size or duration thresholds
Response Consistency CheckerDetects inconsistent responses for the same endpoint across requests
TemplateWhat It Does
Response Body RewriterReplaces text patterns in response bodies (regex-based find/replace)
Request Header InjectorAdds custom headers to outgoing requests
Latency SimulatorIntroduces artificial delay to responses for testing slow network conditions

Each addon has a dedicated log console visible at the bottom of the editor. Messages sent via ghost.log(), ghost.warn(), and ghost.error() appear here in real-time:

  • Info messages (cyan) — from ghost.log()
  • Warning messages (amber) — from ghost.warn()
  • Error messages (red) — from ghost.error()

Each entry shows a timestamp and the message text. The console keeps the last 500 log entries and auto-scrolls to the bottom when new messages arrive. Click the clear button to reset the console.

The console is filtered by the selected addon — each addon’s logs are separate.

Addon lifecycle changes are broadcast to all connected frontends via WebSocket events:

EventWhen It Fires
addon.createdA new addon was saved to the database
addon.updatedAn addon’s code or settings were changed
addon.deletedAn addon was removed
addon.logAn addon produced a log message (ghost.log/warn/error)

These events keep the UI synchronized — if you have multiple Ghost windows open, creating an addon in one window immediately appears in the other.