Docs / Extending / Write a plugin
Write your first plugin
The short version. Crusader plugins are JavaScript, hot-reload while you edit, and community plugins are Free. You register hooks on a crusader.* object (insertion points, session requests, payload generators, scan checks, editor tabs) and reach into the host through an api.* surface (HTTP, identities, beacon, SQL, sitemap, UI). Every plugin declares its capabilities up front; you preview that manifest before installing so there are no surprises. Locked APIs don't crash your plugin — they return a structured requires_upgrade at runtime, so the same plugin runs on Free and unlocks more on Pro.
01The plugin model
A Crusader extension is a JavaScript file. There's no build step and no SDK to vendor: you write against globals the runtime injects — crusader (the hooks you register) and api (the host surface you call). During development the runtime hot-reloads on save, so you edit, hit save, and re-run a check without reinstalling.
Community plugins are Free. Authoring, installing, and running a plugin needs no license. Most capability keys are Free too. A handful of advanced API capabilities are gated to Hunter Pro — scanner findings, signed identity replay, hosted Beacon, MCP tools, report packaging, active scanning, mobile/Frida, and the JA3 transport — and they're listed plainly in section 05. Free is a real daily driver here, not a teaser.
The contract is built around consent before capability. A plugin declares everything it wants in a capabilities array; Crusader shows you that manifest before anything runs; sensitive permissions always require an explicit yes. Nothing reaches the network, your history, or your identities unless you approved the capability that gates it.
Plugins are real code with real reach. Only install extensions you've read or trust — the same way you'd vet a Burp BApp. The preview step exists so you can see exactly what a plugin asks for before you say yes.
02A minimal scan-check plugin
Here's a complete, runnable-looking plugin: a passive scan check that flags responses still served over cleartext http. It does no probing — it reads exchanges Crusader already captured and raises a finding through crusader.report. Note the capabilities declaration at the top: a passive check that only reads and reports needs nothing sensitive.
// cleartext-check.js — passive scan check, Free capabilities only
crusader.capabilities = ["history"]; // read captured exchanges
crusader.scanCheck({
name: "Cleartext transport",
// run() is called per in-scope exchange; return nothing to stay quiet
run(exchange) {
// exchange exposes the captured request/response
if (exchange.scheme !== "http") return;
crusader.report({
title: "Response served over cleartext HTTP",
severity: "low",
url: exchange.url,
evidence: `${exchange.method} ${exchange.url} → ${exchange.status}`,
remediation: "Redirect to HTTPS and set HSTS.",
});
},
});
That's the whole plugin. Drop it in the Extensions screen as a local file (or install it from the CLI), browse your target through the proxy, and run a passive scan — every cleartext exchange becomes a Low finding in Scanner → Findings. Because passive analysis is Free, this check runs on every tier.
Keep first plugins passive. crusader.scanCheck runs over already-captured history — it never sends traffic on its own. Anything that actively probes a target needs the scanner.active capability (Pro) and is for systems you're authorized to test.
03Registration hooks
You extend Crusader by registering callbacks on the crusader object. Each hook plugs into one part of the workstation. Register only what you need — every hook maps to a capability you'll declare and the user will approve.
| Hook | What it does |
|---|---|
crusader.onInsertionPoints(fn) | Define custom scanner fuzz / insertion points on a request — extra places the scanner and Intruder should inject. |
crusader.onSessionRequest(fn) | Session handling before each request goes out — refresh a token, re-sign, or fix up auth headers so replayed traffic stays valid. |
crusader.payloadGenerator({name, generate}) | Feed Intruder a custom payload set from your own generate function. |
crusader.payloadProcessor(fn) | Transform each Intruder payload on the way out — encode, sign, prefix, mutate. |
crusader.scanCheck({name, run}) | A passive check that runs over captured exchanges and raises findings via crusader.report(...). |
crusader.editorTab({title, request, response, render}) | Add a custom tab to the History/Repeater inspector — your own decoder or view of the request/response. |
crusader.report(...) | Raise a finding into the unified findings store. Writing scanner findings is gated by scanner.findings (Pro). |
04The host api.* surface
Where crusader.* registers behavior, api.* is how your plugin reaches into the host — sending requests, reading the project's data, asking about the license, drawing UI. Each call is gated by a capability; calling one you didn't declare (or one locked on your tier) returns a structured refusal rather than throwing — see section 07.
| Call | What it does |
|---|---|
api.request({...}) | Send an HTTP request through Crusader's engine (needs network). |
api.requestAs(identityId, opts) | Send as a saved identity. Respects the identity's scope and only fires when replayable_to_url === true. Signed replay is Pro. |
api.identities({url}) | List the identities applicable to a URL (metadata only — never raw secrets). |
api.beacon({module, tag}) | Mint an out-of-band callback payload. BYO Beacon is Free; hosted Beacon (crusader.sh) is Pro. |
api.license() · api.license({action:'require', feature:'…'}) | Check the active tier, or assert a feature is present before doing gated work. |
api.report(...) · api.output(...) | Raise a finding / write to the plugin's output log. |
api.ui | UI builders for forms and panels (a side panel needs gui.panel, which is Pro). |
api.cli | Register a custom crusader CLI verb (needs cli.verb). |
api.sql · api.history · api.sitemap | Read-only access to the project history (SQL/search), and the discovered site map tree. |
The reads — api.sql, api.history, api.sitemap, api.identities — are the backbone of analysis plugins: pull what Crusader already captured, reason over it, and report. There's also api.transport.request for the JA3 / browser-fingerprint transport, which is Pro (ja3-transport).
Stick to the calls above. The runtime injects exactly this surface — don't assume Node modules, filesystem access, or arbitrary network libraries. If you need to shell out, note that the exec capability is disabled in this build (see section 05); design around it.
05Capabilities & permissions
Every plugin declares a capabilities array. This is the permission manifest: it's what preview surfaces, what you approve at install, and what the runtime enforces at call time. Declare the minimum — a passive analysis plugin usually needs only history and maybe sitemap.
Most capabilities are Free (sensitive ones still require explicit consent on install). A set of advanced capabilities is gated to Hunter Pro. Both are listed here so you know which side of the line your plugin sits on before you build it.
| Tier | Capability keys |
|---|---|
| Free | request.hook, gui.form, context-menu, cli.verb, hot-reload, network, history, sitemap, raw-replay, transport, beacon, mobile.artifact, mobile.discovery (consent-gated where sensitive) |
| Pro | scanner.findings, identity.store, identity.signed-replay, beacon.callbacks, hosted-beacon, ja3-transport, mcp.tools, gui.panel, report.packaging, scanner.active, mobile.frida |
The exec capability (spawning child processes) exists but is DISABLED in this build — child processes can't be scope-constrained, so don't rely on it. Sensitive capabilities always require explicit user consent, regardless of tier; a plugin can't quietly grant itself access to your identities, callbacks, or the network.
06Preview, then install
Always preview before you install — yours or anyone's. Preview parses the plugin and prints its capability manifest without running it, so you see exactly what it asks for and how it behaves on your license:
# inspect a plugin's capabilities without installing it
crusader plugin preview ./cleartext-check.js
# or straight from a URL
crusader plugin preview https://example.com/some-plugin.js
The manifest shows, per capability, whether it's allowed on your tier and what consent it needs. Look for three fields:
| Field | Meaning |
|---|---|
requires_review | The plugin asks for sensitive permissions — install needs your explicit approval. |
api_access_state | Which parts of the api.* surface are open vs. locked on your current license. |
requires_upgrade | Per-capability flag: this key is Pro-gated and inert until you upgrade — but it does not block install. |
Once you're satisfied, install it. A plugin that requests review-required capabilities needs --approve-capabilities on the CLI to confirm you've seen the manifest:
# install, approving the sensitive capabilities you saw in preview
crusader plugin install ./cleartext-check.js --approve-capabilities
You can also load a local file directly from the Extensions screen, where the same consent prompt appears in the UI. During development, leave the file installed and just keep editing — hot reload picks up each save so you iterate without reinstalling.
07How locked APIs behave
This is the part that makes one plugin work on every tier. When a plugin calls a Pro-gated API on Free, the call does not throw. It returns a structured requires_upgrade result — the same shape the MCP server returns for gated tools — so your code can branch on it cleanly:
// a hosted-Beacon call on Free returns a result, not an exception
const r = api.beacon({ module: "ssrf", tag: "probe" });
if (r.ok === false && r.code === "requires_upgrade") {
// r also carries: feature, feature_name, required_tier, message, hint, license
api.output(`${r.feature_name} needs ${r.required_tier} — ${r.hint}`);
return; // degrade gracefully; the plugin stays installed and running
}
// …use r normally when ok
The consequences are worth stating plainly:
- The plugin stays installable and runnable on Free. Gated capabilities are inert, not fatal — the rest of the plugin works.
- You branch, you don't catch. Check
r.ok/r.code === "requires_upgrade"rather than wrapping calls intry/catch. - The result is self-describing. It carries
feature,feature_name,required_tier, a humanmessage, ahint(e.g. runcrusader license status), and alicensesnapshot — enough to tell the user what to do.
Write plugins that degrade gracefully: do the Free work unconditionally, and treat Pro APIs as enhancements that announce themselves when locked. That way the same file ships to everyone and quietly does more on Hunter Pro.
Want a guide that isn't here yet? Email hello@crusaderproxy.com.