Nexus plugins are isolated JavaScript modules that extend Nexus with custom tools, event handlers, and integrations. Plugins run in an isolated-vm sandbox with strict resource limits.
plugins/my-plugin/
├── manifest.json
├── index.js
└── README.md
{
"name": "my-plugin",
"version": "1.0.0",
"description": "Does something useful",
"entry": "index.js",
"permissions": ["fs.read"],
"config": {
"apiUrl": {
"type": "string",
"default": "https://api.example.com",
"description": "API endpoint URL"
},
"timeout": {
"type": "number",
"default": 5000,
"description": "Request timeout in ms"
}
}
}| Permission | Scope | Description |
|---|---|---|
fs.read |
filesystem | Read files from allowed paths |
fs.write |
filesystem | Write files to allowed paths |
net.connect |
network | Make HTTP/HTTPS requests |
process.spawn |
system | Execute child processes |
env.read |
environment | Read environment variables |
// index.js
const plugin = {
name: "my-plugin",
version: "1.0.0",
async init(ctx) {
// Called when plugin is loaded
ctx.logger.info("Plugin initialized")
// Register a tool
ctx.tools.register({
name: "hello",
description: "Says hello to someone",
parameters: {
type: "object",
properties: {
name: {
type: "string",
description: "Name to greet",
},
},
required: ["name"],
},
async execute(args) {
return {
success: true,
data: `Hello, ${args.name}!`,
}
},
})
// Listen for events
ctx.events.on("session:start", (payload) => {
ctx.logger.info(`Session started: ${payload.sessionId}`)
})
},
async destroy(ctx) {
// Cleanup when plugin is unloaded
ctx.logger.info("Plugin destroyed")
},
}
export default plugininterface Logger {
debug(msg: string, ...args: unknown[]): void
info(msg: string, ...args: unknown[]): void
warn(msg: string, ...args: unknown[]): void
error(msg: string, ...args: unknown[]): void
}Persistent key-value store scoped to the plugin.
interface PluginStorage {
get(key: string): Promise<string | undefined>
set(key: string, value: string): Promise<void>
delete(key: string): Promise<void>
list(): Promise<string[]>
}interface ToolAPI {
register(tool: ToolDefinition): void
unregister(name: string): void
list(): ToolDefinition[]
}Built-in events emitted by Nexus:
| Event | Payload | Description |
|---|---|---|
session:start |
{ sessionId } |
Session started |
session:end |
{ sessionId, turns } |
Session ended |
message:before |
{ content } |
Before LLM call |
message:after |
{ content, response } |
After LLM response |
tool:before |
{ name, args } |
Before tool execution |
tool:after |
{ name, result } |
After tool execution |
error |
{ message, stack } |
Unhandled error |
Create a test directory in your plugin:
plugins/my-plugin/
├── manifest.json
├── index.js
├── test/
│ └── index.test.js
Example test:
import { describe, it, expect } from "vitest"
import plugin from "../index.js"
describe("my-plugin", () => {
it("registers hello tool", async () => {
const tools = { register: vi.fn(), unregister: vi.fn(), list: vi.fn() }
const ctx = {
logger: console,
storage: { get: vi.fn(), set: vi.fn(), delete: vi.fn(), list: vi.fn() },
tools,
events: { on: vi.fn(), off: vi.fn(), emit: vi.fn() },
}
await plugin.init(ctx)
expect(tools.register).toHaveBeenCalledWith(
expect.objectContaining({ name: "hello" })
)
})
})- Ensure
manifest.jsonis valid - Publish to npm as a public package
- Package name convention:
nexus-plugin-<name> - Users can install via:
nexus plugin install nexus-plugin-my-plugin
nexus-plugin-my-plugin/
├── package.json # "main" points to index.js, "nexus-plugin" field for manifest
├── manifest.json # Plugin manifest
├── index.js # Plugin implementation
├── test/
│ └── index.test.js
└── README.md
The package.json should include a nexus-plugin field pointing to the manifest:
{
"name": "nexus-plugin-my-plugin",
"version": "1.0.0",
"nexus-plugin": "./manifest.json",
"main": "./index.js"
}