From caido-plugin-dev
Develops Caido plugins for frontend (Vue.js), backend (Rust), GraphQL API, or SQLite integration to extend security testing tools, automation, and web app auditing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/caido-plugin-dev:caido-plugin-devThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Caido is a lightweight web security auditing toolkit designed for penetration testing and vulnerability assessment. This skill provides comprehensive guidance for developing Caido plugins using the official SDK ecosystem.
Caido is a lightweight web security auditing toolkit designed for penetration testing and vulnerability assessment. This skill provides comprehensive guidance for developing Caido plugins using the official SDK ecosystem.
Key Resources:
Caido is a modern web security auditing platform similar to Burp Suite, designed for penetration testers and security researchers. It operates on a client-server architecture and can run locally or on remote instances (VPS, Docker, cloud).
Understanding Caido's core features is essential for plugin development, as plugins often extend or integrate with these existing capabilities:
Proxy & Traffic Management:
Testing and Automation:
Security Analysis:
Data Management:
HTTPQL is the query language we use in Caido to let you filtering requests and responses.
Caido uses HTTPQL for filtering requests throughout the application. Common query examples:
req.ext.eq:".js" - Filter by file extensionresp.code.eq:200 - Filter by status codereq.method.eq:"GET" - Filter by HTTP methodreq.ext.eq:".php" AND resp.code.eq:200 - Combine filters with boolean operatorsUnderstanding HTTPQL is important for plugins that query or filter traffic.
Caido supports WebSocket traffic inspection and manipulation, allowing real-time analysis of WebSocket streams.
Plugins can extend virtually any part of Caido:
Reference: See https://docs.caido.io/llms.txt for complete Caido platform documentation
Caido supports three types of plugins, each with its own SDK:
@caido/sdk-frontend) - UI components, custom pages, commands, context menus@caido/sdk-backend) - Server-side logic, HTTP operations, data processing, findings@caido/sdk-workflow) - JavaScript nodes for automation sequencesPlugins can combine frontend and backend components in a single package. In most cases, we're going to focus on frontend and backend plugins.
IMPORTANT: Always start a new Caido plugin project using the official starter CLI:
pnpm create @caido-community/plugin
This is the standard and recommended way to initialize any new Caido plugin project.
Use the official starter CLI to scaffold a new plugin:
pnpm create @caido-community/plugin
This creates a template with:
caido.config.ts - Plugin configurationmanifest.json - Auto-generated metadataReference: https://developer.caido.io/guides/ (Getting Started section)
pnpm install
pnpm build
This produces dist/plugin_package.zip ready for installation in Caido.
A typical Caido plugin follows this structure:
my-plugin/
├── caido.config.ts # Plugin configuration
├── manifest.json # Auto-generated from config
├── package.json
├── pnpm-workspace.yaml # If using monorepo
├── packages/
│ ├── frontend/ # Frontend plugin code
│ │ ├── src/
│ │ │ ├── index.ts # Entry point
│ │ │ └── types.ts # TypeScript types
│ │ └── style.css # Optional styles
│ └── backend/ # Backend plugin code
│ ├── src/
│ │ └── index.ts # Entry point
│ └── assets/ # Backend-accessible files
└── dist/ # Build output
packages/backend → Backend plugin code - handles server-side logic, data processing, and API endpointspackages/frontend → Frontend plugin code - handles UI components, user interactions, and calls to backend using sdk.backend.functionNameExample: See https://github.com/caido-community/plugin-demo for official demo structure
The primary configuration file that defines your plugin package. Uses defineConfig from @caido-community/dev.
Reference: https://developer.caido.io/reference/config
Common Configuration:
import { defineConfig } from '@caido-community/dev';
export default defineConfig({
id: 'my-plugin', // Unique ID: lowercase, hyphens/underscores
name: 'My Plugin', // Display name
description: 'Plugin description',
version: '1.0.0', // Semantic versioning
author: {
name: 'Your Name',
email: '[email protected]', // Optional
url: 'https://yoursite.com' // Optional
},
plugins: [
// Frontend plugin config
{
kind: 'frontend',
id: 'my-plugin-frontend',
name: 'My Plugin Frontend',
root: './packages/frontend',
backend: {
id: 'my-plugin-backend' // Link to backend plugin
}
},
// Backend plugin config
{
kind: 'backend',
id: 'my-plugin-backend',
name: 'My Plugin Backend',
root: './packages/backend',
assets: ['assets/**/*'] // (optional) Glob patterns for assets
}
]
});
Auto-generated from caido.config.ts during build. Defines plugin metadata and installation structure.
Reference: https://developer.caido.io/reference/manifest
Key Fields:
id - Unique identifier matching ^[a-z][a-z0-9]+(?:[_-][a-z0-9]+)*$version - MAJOR.MINOR.PATCH formatplugins - Array of frontend/backend/workflow definitionsauthor - Creator information (name, email, url)links - Sponsor/funding URLsNote: Manually edit only if not using caido.config.ts
Frontend plugins create UI components, custom pages, commands, and context menus using Vue.js and the Caido frontend SDK. The Caido Frontend SDK is used for creating UI components, pages, and handling user interactions in Caido plugins.
SDK Reference: https://developer.caido.io/reference/sdks/frontend/
Frontend plugins are initialized via packages/frontend/src/index.ts:
import { Caido } from "@caido/sdk-frontend";
import { API, BackendEvents } from "backend";
// Define SDK type with backend API
export type FrontendSDK = Caido<API, BackendEvents>;
// Plugin initialization
export const init = (sdk: FrontendSDK) => {
// Create pages and UI
createPage(sdk);
// Register sidebar items
sdk.sidebar.registerItem("My Plugin", "/my-plugin-page", {
icon: "fas fa-rocket"
});
// Register commands
sdk.commands.register("my-command", {
name: "My Custom Command",
run: () => sdk.backend.myCustomFunction("Hello"),
});
};
The frontend SDK provides styled components that match Caido's design system:
const requestEditor = caido.ui.httpRequestEditor({
id: 'my-request-editor',
options: { readOnly: false }
});
const responseEditor = caido.ui.httpResponseEditor({
id: 'my-response-editor',
options: { readOnly: true }
});
Add custom pages to Caido's navigation:
sdk.navigation.addPage("/my_plugin", {
body: root
});
Navigation:
// Navigate programmatically
sdk.navigation.goTo('/my-plugin');
// Listen for page changes
sdk.navigation.onPageChange((path) => {
console.log('Navigated to:', path);
});
export type FrontendSDK = Caido<Record<string, never>, Record<string, never>>;
import { Caido } from "@caido/sdk-frontend";
import { API, BackendEvents } from "backend";
export type FrontendSDK = Caido<API, BackendEvents>;
Commands provide a unified way to register actions that can be triggered from:
Commands is a frontend-only concept.
// Define command IDs as constants
const Commands = {
processData: "my-plugin.process-data",
exportResults: "my-plugin.export-results",
} as const;
// Register commands
sdk.commands.register(Commands.processData, {
name: "Process Data",
run: async () => {
const result = await sdk.backend.processData();
sdk.window.showToast(`Processed ${result.count} items`, {
variant: "success"
});
},
group: "My Plugin",
});
// Add to command palette
sdk.commandPalette.register(Commands.processData);
// Add to context menus
sdk.menu.registerItem({
type: "Request",
commandId: Commands.processData,
leadingIcon: "fas fa-cog",
});
Add commands to context menus in various Caido views:
// Request context menu
sdk.menu.registerItem({
type: 'Request',
commandId: 'my-plugin:process-request',
leadingIcon: 'fas fa-cog'
});
// Response context menu
sdk.menu.registerItem({
type: 'Response',
commandId: 'my-plugin:process-response'
});
// Request row context menu (in tables)
sdk.menu.registerItem({
type: 'RequestRow',
commandId: 'my-plugin:analyze-request'
});
// Settings menu
sdk.menu.registerItem({
type: 'Settings',
commandId: 'my-plugin:open-settings'
});
Store plugin configuration and data:
// Define storage type
type MyStorage = {
notes: Array<{
content: string;
timestamp: string;
}>;
settings: {
enabled: boolean;
};
};
// Get data
const data = await sdk.storage.get<MyStorage>();
// Set data
await sdk.storage.set<MyStorage>({
notes: [...],
settings: { enabled: true }
});
// Listen for changes
sdk.storage.onChange<MyStorage>((newData) => {
console.log('Storage updated:', newData);
});
Example: See https://developer.caido.io/tutorials/notebook for a complete storage example
Call backend functions and subscribe to events:
// Call backend function
const result = await sdk.backend.myBackendFunction('param');
// Subscribe to backend events
sdk.backend.onEvent("my_plugin:request_received", (data) => {
console.log(data)
});
When calling backend APIs from the frontend, handle Result types gracefully:
// Frontend usage - no try/catch needed
const handleProcess = async () => {
const result = await sdk.backend.processData(inputValue);
if (result.kind === "Error") {
sdk.window.showToast(result.error, { variant: "error" });
return;
}
// Handle successful result
const data = result.value;
sdk.window.showToast("Processing completed!", { variant: "success" });
};
Access Caido environment variables:
const apiKey = await sdk.env.getVar('API_KEY');
Types:
/**
* Utilities to interact with the environment.
* @category Environment
*/
export type EnvironmentSDK = {
/**
* Get the value of an environment variable.
* @param name The name of the environment variable.
* @returns The value of the environment variable.
*/
getVar: (name: string) => string | undefined;
/**
* Get all environment variables available in the global environment and the selected environment.
* @returns All environment variables.
*/
getVars: () => EnvironmentVariable[];
};
export type EnvironmentVariable = {
/**
* The name of the environment variable.
*/
name: string;
/**
* The value of the environment variable.
*/
value: string;
/**
* Whether the environment variable is a secret.
*/
isSecret: boolean;
};
Caido provides specialized SDKs for extending specific pages:
HTTP History:
sdk.httpHistory.setQuery("resp.raw.cont:\"hello\"")
Search:
sdk.search.setQuery("resp.raw.cont:\"hello\"")
Replay:
const session = await sdk.replay.createSession({
name: 'My Session',
requests: [requestId1, requestId2]
});
/**
* Create a session.
* @param sessionId The ID of the request to add.
* @param collectionId The ID of the collection to add the request.
*/
createSession: (source: RequestSource, collectionId?: ID) => Promise<void>;
/**
* @category Replay
*
* @remarks
* This type is a discriminated union with two possible shapes:
* - A raw request, containing the raw HTTP request string and connection information.
* - A reference to an existing request ID.
*
* @example
* // Using a raw request
* const source: RequestSource = {
* type: "Raw",
* raw: "GET /api/data HTTP/1.1",
* connectionInfo: { ... }
* };
* // Using an ID
* const source: RequestSource = {
* type: "ID",
* id: "request-123"
* };
*/
export type RequestSource = {
type: "Raw";
raw: string;
connectionInfo: SendRequestOptions["connectionInfo"];
} | {
type: "ID";
id: string;
};
/**
* The connection information to use for the request.
*/
connectionInfo: {
/**
* The host to use for the request.
*/
host: string;
/**
* The port to use for the request.
*/
port: number;
/**
* Whether the request is TLS.
*/
isTLS: boolean;
/**
* The SNI to use for the request.
* If not provided, the SNI will be inferred from the host.
*/
SNI?: string;
};
Findings:
const finding = await sdk.findings.createFinding(requestId, {
title: 'XSS Vulnerability',
description: 'Reflected XSS found',
reporter: 'My Plugin'
});
Automate, Intercept, Sitemap: See SDK docs for specialized methods
Toasts:
sdk.window.showToast("Hello", {
variant: 'success', // "success" | "error" | "warning" | "info";
duration: 3000
});
Get content from the currently focused editor:
const editor = sdk.window.getActiveEditor();
if (editor) {
const content = editor.getContent();
const selection = editor.getSelection();
}
UI slots allow adding buttons, commands, or custom components to predefined locations in Caido's interface:
import { FooterSlot } from "@caido/sdk-frontend";
// Add a button to the footer
sdk.ui.addToSlot(FooterSlot.FooterSlotPrimary, {
component: MyCustomButton,
props: { label: "My Action" }
});
Available slot locations include footer areas and replay toolbars. Pass slot identifiers to addToSlot() to specify placement.
Custom request view modes display requests in alternative formats across multiple pages (HTTP History, Replay, Search, etc.):
// Register a custom view mode
sdk.ui.addRequestViewMode({
id: "my-custom-view",
name: "Custom View",
component: MyViewComponent
});
The Vue component receives sdk, request, and optionally requestDraft props.
The command palette supports pushing custom Vue components for multi-step wizards and custom search interfaces:
// Push a custom view to the command palette
sdk.commandPalette.pushView({
component: MyCustomSearchComponent
});
Components automatically receive the SDK as a prop and can emit events and update state reactively.
Register keyboard shortcuts for commands:
sdk.shortcuts.register("my-plugin.action", {
key: "ctrl+shift+a",
mac: "cmd+shift+a"
});
Complete Frontend SDK Reference: https://developer.caido.io/reference/sdks/frontend/
Backend plugins handle server-side logic, HTTP operations, data processing, findings generation, and more. They run in a QuickJS runtime with Node.js-compatible APIs.
SDK Reference: https://developer.caido.io/reference/sdks/backend/
Backend plugins are initialized via packages/backend/src/index.ts:
import { SDK, DefineAPI } from "caido:plugin";
// Define your API functions
function myCustomFunction(sdk: SDK, param: string) {
sdk.console.log(`Called with: ${param}`);
return `Processed: ${param}`;
}
// Export the API type definition
export type API = DefineAPI<{
myCustomFunction: typeof myCustomFunction;
}>;
// Plugin initialization
export function init(sdk: SDK<API>) {
// Register API endpoints
sdk.api.register("myCustomFunction", myCustomFunction);
}
import { DefineEvents, SDK } from "caido:plugin";
export type BackendEvents = DefineEvents<{
"data-updated": { message: string };
"status-changed": { status: "active" | "inactive" };
}>;
export type CaidoBackendSDK = SDK<never, BackendEvents>;
When building API endpoints in the backend and calling them from the frontend, use Result types to handle errors gracefully without throwing exceptions:
// Define the Result type
export type Result<T> =
| { kind: "Error"; error: string }
| { kind: "Ok"; value: T };
// Backend API function returning Result
function processData(sdk: SDK, input: string): Result<ProcessedData> {
try {
// Your processing logic here
const processed = doSomeProcessing(input);
return { kind: "Ok", value: processed };
} catch (error) {
return { kind: "Error", error: error.message };
}
}
// Frontend usage - no try/catch needed
const handleProcess = async () => {
const result = await sdk.backend.processData(inputValue);
if (result.kind === "Error") {
sdk.window.showToast(result.error, { variant: "error" });
return;
}
// Handle successful result
const data = result.value;
sdk.window.showToast("Processing completed!", { variant: "success" });
};
// Define multiple API functions
function getData(sdk: SDK, id: string): Result<Data> {
// Implementation
}
function saveData(sdk: SDK, data: Data): Result<void> {
// Implementation
}
function deleteData(sdk: SDK, id: string): Result<boolean> {
// Implementation
}
// Export Caido Backend API
export type API = DefineAPI<{
getData: typeof getData;
saveData: typeof saveData;
deleteData: typeof deleteData;
}>;
// Register all endpoints
export function init(sdk: SDK<API>) {
sdk.api.register("getData", getData);
sdk.api.register("saveData", saveData);
sdk.api.register("deleteData", deleteData);
}
Send Events to Frontend:
sdk.api.send('eventName', eventData);
Creating and Sending Requests
import { RequestSpec } from "caido:utils";
import { type Request, type Response } from "caido:utils";
// Create a new request
const spec = new RequestSpec("https://api.example.com/data");
spec.setMethod("POST");
spec.setHeader("Content-Type", "application/json");
spec.setBody(JSON.stringify({ key: "value" }));
// Send the request
const result = await sdk.requests.send(spec);
if (result.response) {
const statusCode = result.response.getCode();
const responseBody = result.response.getBody()?.toText();
}
Get Saved Requests by ID:
const request = await sdk.requests.get(requestId);
Check Request Scope:
const inScope = await sdk.requests.inScope(requestUrl);
Test HTTPQL Filters:
const matches = await sdk.requests.matches(request, 'ext:php AND status:200');
Request:
const method = request.getMethod();
const url = request.getUrl();
const path = request.getPath();
const query = request.getQuery();
const headers = request.getHeaders();
const body = request.getBody();
// Convert body
const bodyText = body?.toText(); // Returns string (unprintable → �)
const bodyJson = body?.toJson(); // Parse as JSON
const bodyRaw = body?.toRaw(); // Get raw bytes
// Get specific header (case-insensitive)
const contentType = request.getHeader('content-type');
// Convert to mutable spec
const spec = request.toSpec();
spec.setHeader('X-Custom', 'value');
Response:
const code = response.getCode();
const statusLine = response.getStatusLine();
const headers = response.getHeaders();
const body = response.getBody();
Intercept and modify requests/responses in real-time:
sdk.events.onInterceptRequest(async (sdk, request) => {
// Modify request
const spec = request.toSpec();
spec.setHeader('X-Intercepted', 'true');
return spec;
});
sdk.events.onInterceptResponse(async (sdk, response) => {
// Modify response
const spec = response.toSpec();
// Modify as needed
return spec;
});
export type Request = {
getId(): ID;
getHost(): string;
getPort(): number;
getTls(): boolean;
getMethod(): string;
getPath(): string;
getQuery(): string;
getUrl(): string;
getHeaders(): Record<string, Array<string>>;
getHeader(name: string): Array<string> | undefined;
getBody(): Body | undefined;
getRaw(): RequestRaw;
getCreatedAt(): Date;
toSpec(): RequestSpec;
toSpecRaw(): RequestSpecRaw;
};
export type Response = {
getId(): ID;
getCode(): number;
getHeaders(): Record<string, Array<string>>;
getHeader(name: string): Array<string> | undefined;
getBody(): Body | undefined;
getRaw(): ResponseRaw;
getRoundtripTime(): number;
getCreatedAt(): Date;
};
For Body and Raw you can use methods like getBody()?.toText() to extract text content.
These types can be imported by:
import { type Request, type Response } from "caido:utils";
SQLite Database:
const db = sdk.meta.db();
// Execute queries with connection pooling
await db.execute('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, content TEXT)');
const rows = await db.query('SELECT * FROM notes');
Plugin Data Directory:
const dataPath = sdk.meta.path(); // Plugin-specific writable directory
const assetsPath = sdk.meta.assetsPath(); // Read-only assets from package
Current Project:
const project = sdk.projects.getCurrent();
const projectName = project?.getName();
const projectPath = project?.getPath();
Scope Management:
const scopes = await sdk.scope.getAll();
export type Scope = {
/**
* The unique Caido {@link ID} of the scope.
*/
readonly id: ID;
/**
* The name of the scope.
*/
readonly name: string;
/**
* The allowlist of the scope.
*/
readonly allowlist: string[];
/**
* The denylist of the scope.
*/
readonly denylist: string[];
};
/**
* The SDK for the Scope service.
* @category Scope
*/
export type ScopeSDK = {
/**
* Get all the scopes.
* @returns An array of {@link Scope}
*/
getAll(): Promise<Scope[]>;
};
Execute GraphQL queries against Caido's internal API:
const result = await sdk.graphql.execute(`
query {
requests(limit: 10) {
nodes {
id
method
url
}
}
}
`);
Spawn external processes (e.g., security tools):
import { spawn } from 'child_process';
const proc = spawn('nmap', ['-sV', 'target.com']);
proc.stdout.on('data', (data) => {
sdk.console.log(data.toString());
});
proc.on('close', (code) => {
sdk.console.log(`Process exited with code ${code}`);
});
Project Change:
sdk.events.onProjectChange((sdk, project) => {
sdk.console.log('Project changed:', project?.name);
});
sdk.console.log('Info message');
sdk.console.debug('Debug message');
sdk.console.warn('Warning message');
sdk.console.error('Error message');
Complete Backend SDK Reference: https://developer.caido.io/reference/sdks/backend/
Backend plugins run in a QuickJS environment with Node.js-compatible modules:
Available Modules: https://developer.caido.io/reference/modules/
caido:http)Provides Fetch API implementations:
import { fetch, Request, Response, Headers } from "caido:http";
// Make HTTP requests
const response = await fetch("https://api.example.com/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "value" })
});
const data = await response.json();
// Work with Headers
const headers = new Headers();
headers.set("Authorization", "Bearer token");
headers.append("Accept", "application/json");
Response methods: text(), json(), arrayBuffer(), blob(), bytes()
llrt/fs)import { readFile, writeFile, mkdir, stat, unlink } from "llrt/fs";
// Read file
const content = await readFile("/path/to/file.txt", "utf-8");
// Write file
await writeFile("/path/to/output.txt", "content");
// Create directory
await mkdir("/path/to/dir", { recursive: true });
// Check file exists
const stats = await stat("/path/to/file");
Constants: F_OK, R_OK, W_OK, X_OK for access checks
import { spawn } from "child_process";
// Note: exec() is unavailable - use spawn with shell: true instead
const proc = spawn("nmap", ["-sV", "target.com"], { shell: true });
proc.stdout.on("data", (data) => {
sdk.console.log(data.toString());
});
proc.on("close", (code) => {
sdk.console.log(`Exited with code ${code}`);
});
Limitations: Stream pipe() is not supported.
import { createHash, createHmac, randomBytes } from "crypto";
// Hashing
const hash = createHash("sha256").update("data").digest("hex");
// HMAC
const hmac = createHmac("sha256", "secret").update("data").digest("hex");
// Random bytes
const bytes = randomBytes(16);
import { Buffer } from "buffer";
const buf = Buffer.from("hello", "utf-8");
const base64 = buf.toString("base64");
const hex = buf.toString("hex");
// Concatenate buffers
const combined = Buffer.concat([buf1, buf2]);
import { join, resolve, dirname, basename, extname } from "path";
const fullPath = join("/base", "dir", "file.txt");
const dir = dirname("/path/to/file.txt"); // "/path/to"
const file = basename("/path/to/file.txt"); // "file.txt"
const ext = extname("file.txt"); // ".txt"
import process from "process";
const cwd = process.cwd();
const apiKey = process.env.API_KEY;
import os from "os";
const platform = os.platform(); // "darwin", "linux", "win32"
const home = os.homedir();
const tmp = os.tmpdir();
import { Database } from "sqlite";
const db = new Database("/path/to/db.sqlite");
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
const user = stmt.get(userId);
// Transactions
db.transaction(() => {
db.run("INSERT INTO users (name) VALUES (?)", "Alice");
db.run("INSERT INTO users (name) VALUES (?)", "Bob");
});
// Available globally, no import needed
const timeoutId = setTimeout(() => {
sdk.console.log("Delayed execution");
}, 1000);
const intervalId = setInterval(() => {
sdk.console.log("Repeated execution");
}, 5000);
clearTimeout(timeoutId);
clearInterval(intervalId);
const controller = new AbortController();
const signal = controller.signal;
// Use with fetch
fetch(url, { signal }).catch(err => {
if (err.name === "AbortError") {
sdk.console.log("Request was cancelled");
}
});
// Cancel the request
controller.abort();
Global Modules (no import needed):
console - Loggingtimers - setTimeout, setIntervalabort - AbortController, AbortSignaldom-events - EventTarget, EventListenerNote: Some modules use caido: prefix (e.g., caido:http, caido:plugin, caido:utils)
Static assets can be bundled with plugins and accessed at runtime. Configure assets in caido.config.ts:
// caido.config.ts
{
kind: 'backend',
id: 'my-plugin-backend',
root: './packages/backend',
assets: ['./assets/**/*'] // Glob patterns for assets
}
// Get an asset file
const asset = await sdk.assets.get("path/to/file.txt");
// Convert to different formats
const text = asset.asString(); // Text content
const json = asset.asJson<ConfigType>(); // Typed JSON
const buffer = asset.asArrayBuffer(); // Binary data
const stream = asset.asReadableStream(); // Stream chunks
Assets are read-only and bundled with the plugin package. Use sdk.meta.assetsPath() to get the assets directory path.
JavaScript's UTF-8 encoding can corrupt raw binary bytes. Use these patterns to preserve exact byte sequences:
// Get raw bytes (preserves binary data)
const rawBytes = request.getPath({ raw: true }); // Returns Uint8Array
// Manipulate as needed
const modified = new Uint8Array([...rawBytes, 0x00]);
// Set back (preserves exact bytes)
spec.setPath(modified);
// For body data
const bodyRaw = request.getBody()?.toRaw(); // Get raw bytes
Important: Use .toText() only when you need string representation. Use .toRaw() when preserving exact bytes is critical.
For JavaScript nodes within Caido's workflow system.
SDK Reference: https://developer.caido.io/reference/sdks/workflow/
Key Capabilities:
Workflow Plugins in manifest.json:
{
"kind": "workflow",
"id": "my-workflow",
"definition": "workflow-definition.json"
}
Community Workflows: https://github.com/caido-community/workflows
For rapid iteration without rebuilding:
Install Devtools from the Caido Community Store
Run Watch Mode:
pnpm watch
Changes are automatically reloaded in real-time.
Reference: https://developer.caido.io/guides/ (Development Workflow section)
Build:
pnpm build
Output: dist/plugin_package.zip
Install in Caido:
plugin_package.zipIMPORTANT: Always run these commands before committing your code:
pnpm lint
pnpm typecheck
These commands ensure code quality and catch type errors before they make it into version control. Make it a habit to run both commands before every commit.
Use sdk.console methods for logging:
Setup Repository: Follow guidelines at https://developer.caido.io/guides/distribution/repository
Submit Plugin: Follow process at https://developer.caido.io/guides/distribution/store
Plugin Requirements:
manifest.json with all required fieldsCaido supports plugin signing for security. Refer to SDK documentation for signing requirements.
When publishing plugins to the Caido store, adhere to these requirements:
Prohibited:
Required Disclosures:
Licensing:
Learn from existing community plugins to understand implementation patterns:
plugin-demo (Official Demo): https://github.com/caido-community/plugin-demo
Notebook (Tutorial Example): https://developer.caido.io/tutorials/notebook
Utilize these as references for adding specific features, so you will know how to implement similar functionality.
quickssrf (31 stars): https://github.com/caido-community/quickssrf
scanner (28 stars): https://github.com/caido-community/scanner
shift (30 stars): https://github.com/caido-community/shift
authmatrix (6 stars): https://github.com/caido-community/authmatrix
devtools: https://github.com/caido-community/devtools
create-plugin: https://github.com/caido-community/create-plugin
Always define types for backend API and events:
// Frontend
type API = {
functionName: (param: ParamType) => Promise<ReturnType>;
};
type Events = {
eventName: (data: EventDataType) => void;
};
const caido: Caido<API, Events>;
try {
const result = await sdk.requests.send({ request: spec });
// Process result
} catch (error) {
sdk.console.error('Request failed:', error);
sdk.api.send('error', { message: error.message });
}
secret: true for sensitive environment variablesPlugin Not Loading:
Hot Reload Not Working:
pnpm watch is runningBackend/Frontend Communication Failing:
Build Failures:
pnpm install to ensure dependencies are up-to-datepnpm tscCursor IDE supports loading Caido documentation directly:
https://developer.caido.io/This enables Cursor's AI to reference Caido SDK documentation when assisting with plugin development.
Caido provides optimized documentation for AI/LLM consumption:
Full Documentation: https://developer.caido.io/llms-full.txt
Summary Documentation: https://developer.caido.io/llms.txt
A custom GPT trained on Caido resources is available for high-accuracy answers to development questions. Check the Caido Discord or documentation for access.
Official Documentation:
SDK References (Always Check for Latest APIs):
Configuration Files:
Guides (18 Frontend Guides Available):
Community Resources:
Development Tools:
pnpm create @caido-community/pluginNote: Caido's plugin ecosystem is actively developed. Always reference the official documentation links above for the most up-to-date API information, as SDKs and capabilities may evolve over time.
any type.undefined over null.else statements where possible.try / catch where possible....-surface-... f.e. border-surface-700.bg-surface-800 as the main app background, bg-surface-700 is the background used for Card component.Splitter and SplitterPanel for vertical or horizontal layout.Card PrimeVue components a lot, if needed add h-full to them via pt params.<template #content> and other named slots (like #header, #footer) in Card and other PrimeVue components that support them.Example:
<Card
class="h-full"
:pt="{
body: { class: 'h-full p-0' },
content: { class: 'h-full flex flex-col' },
}"
>
<template #content>
...
</template #content>
</Card>
DataTable component for displaying structured data:
stripedRows, use it where possiblefas fa-[...] for icons. We don't support any other icon libraries.Always use documented Caido SDK APIs directly without runtime checks or validation.
Good
sdk.window.showToast("Message", { variant: "error" });
sdk.backend.myFunction();
sdk.commands.register("my-command", { name: "Command", run: () => {} });
Bad (NEVER do this)
if ("showToast" in sdk.window && typeof sdk.window.showToast === "function") {
sdk.window.showToast("Message");
}
typeof, in operator)The SDK is typed and guaranteed to have the documented methods available.
We have a built-in ESLint linter configured at the root folder. After making any significant change, always run the linter with pnpm lint and fix all potential issues.
To prevent this, when comparing strings, instead of writing:
if (!str) {}
do this:
if (str !== undefined) {}
<script setup lang="ts"> for all components.Use the following layout for every component to keep imports and growth consistent:
ComponentName/
├─ index.ts # Re-export (single public entry)
├─ Container.vue # Main component implementation
├─ useForm.ts # Optional: composable when logic grows (e.g. forms)
└─ DependentComponent.vue
ComponentName/index.ts
export { default as ComponentName } from "./Container.vue";
When a child piece becomes complex or needs its own hook, use the same pattern as the parent:
ComponentName/
└─ DependentComponent/
├─ index.ts
└─ Container.vue
npx claudepluginhub caido-community/caido-plugin-skillBuilds UI pages and extensions for Falcon Foundry apps using React or Vue with the Shoelace design system and Foundry-JS. Scaffolds pages, extensions, and navigation via the Foundry CLI.
Guides Claude Code plugin creation, directory structure, plugin.json manifest setup, component organization for commands/agents/skills/hooks/lspServers, file conventions, and best practices like auto-discovery and CI integration.
Orchestrates Claude Code plugin lifecycle: create new plugins from concepts, improve existing via assessment, research, design, creation, debugging, optimization, verification.