Documentation

Creating Plugins

Learn how to create custom Frame-Master plugins and contribute to the ecosystem.

🚀 Quick Start

Generate a plugin boilerplate using the Frame-Master CLI.

terminal
# Generate a new plugin
frame-master plugin create my-custom-plugin
# This creates:
# frame-master-plugin-my-custom-plugin/
# ├── index.ts # Plugin entry point
# ├── package.json # Plugin metadata
# ├── README.md # Documentation
# └── tsconfig.json # TypeScript config
💡

Plugin Naming Convention

Use the format frame-master-plugin-[name] for consistency with the ecosystem.

📦 Basic Plugin Structure

Every Frame-Master plugin exports a factory function that returns a FrameMasterPlugin object.

my-plugin.ts
import type { FrameMasterPlugin } from "frame-master/plugin/types";
export type MyPluginOptions = {
apiKey?: string;
debug?: boolean;
};
export function myPlugin(options: MyPluginOptions = {}): FrameMasterPlugin {
const config = {
apiKey: options.apiKey || process.env.API_KEY,
debug: options.debug ?? false,
};
return {
// Required fields
name: "my-custom-plugin",
version: "1.0.0",
// Optional: Execution priority (lower = runs first)
priority: 50,
// Server lifecycle hooks
serverStart: {
main: async () => { /* runs on startup */ },
dev_main: async () => { /* dev mode only */ },
},
// Request handling hooks
router: {
before_request: async (master) => { /* init context */ },
request: async (master) => { /* handle/intercept */ },
after_request: async (master) => { /* modify response */ },
html_rewrite: { /* transform HTML */ },
},
// Build hooks
build: {
buildConfig: async (builder) => ({ /* merge config */ }),
beforeBuild: async (config, builder) => { /* pre-build */ },
afterBuild: async (config, result, builder) => { /* post-build */ },
},
// WebSocket support
serverConfig: { routes: { /* ws upgrade routes */ } },
websocket: { onOpen, onMessage, onClose },
// File watching (dev mode)
fileSystemWatchDir: ["src/"],
onFileSystemChange: async (event, path) => { /* react */ },
// CLI extension
cli: (command) => command.command("my-cmd").action(() => {}),
};
}
export default myPlugin;

Required Fields

namestringrequired

Unique identifier for your plugin. Should match package name for consistency.

versionstringrequired

Semantic version (e.g., '1.0.0'). usually matches package.json.

💡

Hook Reference

For detailed documentation on all available hooks, see the Plugin Hooks Reference.

📋 Plugin Requirements

Specify dependencies and version requirements.

requirements.ts
requirement: {
// Minimum Frame-Master version
frameMasterVersion: "^2.0.0",
// Bun runtime version
bunVersion: ">=1.2.0",
// Required plugins
frameMasterPlugins: {
"frame-master-plugin-session": "^1.0.0",
},
}
⚠️

Validation

Frame-Master validates requirements at startup. Missing dependencies prevent the server from starting.

🔌 WebSocket Support

Add real-time communication to your plugin.

websocket.ts
serverConfig: {
routes: {
"/ws/my-plugin": (req, server) => {
return server.upgrade(req, {
data: { "my-plugin-ws": true },
});
},
},
},
websocket: {
onOpen: async (ws) => {
if (!ws.data["my-plugin-ws"]) return;
ws.send(JSON.stringify({ type: "connected" }));
},
onMessage: async (ws, message) => {
if (!ws.data["my-plugin-ws"]) return;
const data = JSON.parse(message.toString());
// Handle message...
},
onClose: async (ws) => {
if (!ws.data["my-plugin-ws"]) return;
// Cleanup...
},
}
💡

Shared Handlers

WebSocket handlers receive connections from all plugins. Use ws.data to identify your plugin's connections.

💻 CLI Extension

Add custom commands to Frame-Master CLI.

cli.ts
cli: (command) => {
return command
.command("generate <type>")
.description("Generate project files")
.option("-o, --output <dir>", "Output directory", "./")
.action(async (type, options) => {
console.log(`Generating ${type} in ${options.output}`);
// Your generation logic
});
}
💡

Command Access

Commands are available via frame-master extended-cli <command>. Uses Commander.js.

👁️ File Watching

React to file changes in development mode.

file-watch.ts
// Directories to watch
fileSystemWatchDir: ["src/styles/", "config/"],
// Handle changes
onFileSystemChange: async (eventType, filePath, absolutePath) => {
if (filePath.endsWith(".css")) {
await rebuildStyles();
}
if (filePath.includes("config/")) {
await reloadConfig();
}
}

📦 Publishing Your Plugin

Share your plugin with the community.

Package.json Setup

package.json
{
"name": "frame-master-plugin-my-plugin",
"version": "1.0.0",
"description": "Description of what your plugin does",
"main": "index.ts",
"type": "module",
"keywords": ["frame-master", "frame-master-plugin"],
"peerDependencies": {
"frame-master": "^2.0.0"
}
}

Publishing Steps

terminal
# Update version
npm version patch # or minor, or major
# Publish to npm
npm publish
# Tag release on GitHub
git tag v1.0.0 && git push --tags

Plugin Checklist

  • ✅ Clear README.md documentation
  • ✅ TypeScript type definitions
  • ✅ Example configuration
  • ✅ Proper versioning (semver)

✨ Best Practices

Tips for creating high-quality plugins.

  • Provide sensible defaults for all options
  • Export TypeScript types for configuration
  • Handle errors gracefully - don't crash the server
  • Cache expensive operations
  • Document all configuration options
  • Use priority values appropriately (auth: 0-10, logging: 80-100)

🎯Next Steps