Plugin Hooks Reference
Complete reference guide for all available hooks in Frame-Master plugins.
📋 Quick Reference
Overview of all available hooks organized by category.
Server Hooks
2 hooksInitialization and startup
serverStart.mainserverStart.dev_mainRouter Hooks
4 hooksRequest processing
router.before_requestrouter.requestrouter.after_requestrouter.html_rewriteBuild Hooks
3 hooksBuild process control
build.buildConfigbuild.beforeBuildbuild.afterBuildFile System Hooks
2 hooksFile change watching
fileSystemWatchDironFileSystemChangeConfiguration
4 propsPlugin metadata
name, versionpriority, requirementWebSocket
3 hooksReal-time communication
websocket.onOpenwebsocket.onMessagewebsocket.onCloseAdvanced
4 propsCustom functionality
serverConfig, clidirectives, runtimePlugins🚀 Server Lifecycle Hooks
Hooks that execute during server initialization and startup.
serverStart.main
serverStart.main() => Promise<void>Executes on the main thread when the server starts. Runs in both development and production modes.
export function myPlugin(): FrameMasterPlugin {return {name: "my-plugin",serverStart: {main: async () => {// Initialize database connectionsawait db.connect();// Load configurationconst config = await loadConfig();// Set up global stateglobal.appConfig = config;console.log("Plugin initialized");},},};}
serverStart.dev_main
serverStart.dev_main() => Promise<void>Executes only in development mode. Use for dev-specific initialization like file watchers or debug tools.
serverStart: {dev_main: async () => {// Enable debug loggingenableDebugMode();// Start file watcherwatchForChanges();// Initialize hot reloadsetupHotReload();console.log("Dev mode initialized");},}
🔀 Router Hooks
Hooks for intercepting and modifying HTTP requests and responses.
router.before_request
router.before_request(master: RequestManager) => Promise<void>Called before request processing begins. Use to initialize context or set global values.
Available Methods:
master.setContext(data)- Set request-specific context datamaster.setGlobalValues(values)- Inject global values accessible in client codemaster.request- Access the incoming Request object
router: {before_request: async (master) => {// Initialize contextmaster.setContext({requestId: crypto.randomUUID(),startTime: Date.now(),user: null,});// Inject global values (accessible as globalThis.__API_URL__)master.setGlobalValues({__API_URL__: process.env.API_URL,__VERSION__: "1.0.0",});},}
router.request
router.request(master: RequestManager) => Promise<void>Called during request processing. Can intercept and handle requests or let them pass through.
Available Methods:
master.request- The incoming Request objectmaster.setResponse(body, options)- Set the response body and optionsmaster.sendNow()- Send response immediately, skipping other pluginsmaster.getContext()- Get request context data
router: {request: async (master) => {const url = new URL(master.request.url);// Handle API routesif (url.pathname.startsWith("/api/")) {const data = await handleApiRequest(master.request);master.setResponse(JSON.stringify(data), {status: 200,header: {"Content-Type": "application/json","X-Custom-Header": "value",},}).sendNow(); // Skip remaining pluginsreturn;}// Let other plugins handle it},}
sendNow() Behavior
Calling sendNow() immediately sends the response and prevents subsequent request hooks from executing. Only use this when you want to bypass the normal request flow.
router.after_request
router.after_request(master: RequestManager) => Promise<void>Called after request processing. Use to modify response headers or perform cleanup.
Available Methods:
master.response- The Response object (may be null)master.request- The original Request objectmaster.getContext()- Get request context data
router: {after_request: async (master) => {const response = master.response;if (!response) return;// Add security headersresponse.headers.set("X-Frame-Options", "DENY");response.headers.set("X-Content-Type-Options", "nosniff");response.headers.set("X-XSS-Protection", "1; mode=block");// Add timing informationconst context = master.getContext();const duration = Date.now() - context.startTime;response.headers.set("X-Response-Time", `${duration}ms`);// Log requestconsole.log(`[${master.request.method}] ${master.request.url} - ${duration}ms`);},}
router.html_rewrite
router.html_rewrite{ initContext, rewrite, after }Transform HTML responses using Bun's HTMLRewriter API.
router: {html_rewrite: {// Initialize context for HTML rewritinginitContext: (req) => {return {injectAnalytics: process.env.NODE_ENV === "production",theme: req.headers.get("x-theme") || "dark",userId: req.headers.get("x-user-id"),};},// Rewrite HTML elementsrewrite: async (reWriter, master, context) => {// Inject scripts in headreWriter.on("head", {element(element) {if (context.injectAnalytics) {element.append('<script src="/analytics.js"></script>',{ html: true });}},});// Modify body attributesreWriter.on("body", {element(element) {element.setAttribute("data-theme", context.theme);if (context.userId) {element.setAttribute("data-user", context.userId);}},});// Transform specific elementsreWriter.on("img", {element(element) {const src = element.getAttribute("src");if (src && !src.startsWith("http")) {element.setAttribute("loading", "lazy");}},});},// Final processing after HTML rewriteafter: async (HTML, master, context) => {// Additional HTML transformationsconsole.log("HTML processing complete");// You can modify HTML string here if needed// return modifiedHTML;},},}
HTMLRewriter API
The reWriter parameter is Bun's HTMLRewriter instance. You can use all standard HTMLRewriter methods to transform HTML.
🔨 Build Hooks
Hooks for customizing and participating in the build process.
build.buildConfig
build.buildConfig(builder: Builder) => Promise<Partial<BuildConfig>>Return build configuration to merge with the global config. All plugins' configs are merged.
build: {buildConfig: async (builder) => {return {// External modules (won't be bundled)external: ["react", "react-dom"],// Minification settingsminify: process.env.NODE_ENV === "production",// Source mapssourcemap: "external",// Code splittingsplitting: true,// Define replacementsdefine: {"__VERSION__": JSON.stringify("1.0.0"),"__BUILD_TIME__": JSON.stringify(new Date().toISOString()),},// Bun plugins for the buildplugins: [myBunPlugin],};},}
Config Merging
All plugins' build configs are merged together. Array fields like external and plugins are concatenated. Object fields like define are merged (later wins).
build.beforeBuild
build.beforeBuild(config: BuildConfig, builder: Builder) => Promise<void>Called before the build starts. Use for pre-build tasks like cleaning or generating files.
build: {beforeBuild: async (config, builder) => {console.log("🔨 Preparing build...");// Generate type definitionsawait generateTypes();// Generate route manifestawait generateRouteManifest();// Clean temp filesawait cleanTempDirectory();// Pre-compile assetsawait compileAssets();},}
Parallel Execution
All plugins' beforeBuild hooks run in parallel for better performance.
build.afterBuild
build.afterBuild(config: BuildConfig, result: BuildOutput, builder: Builder) => Promise<void>Called after successful build. Use for post-processing, generating manifests, or custom file operations.
build: {afterBuild: async (config, result, builder) => {if (!result.success) return;console.log(`✅ Built ${result.outputs.length} files`);// Generate manifestconst manifestPath = `${config.outdir}/manifest.json`;const manifest = {version: "1.0.0",buildTime: new Date().toISOString(),files: result.outputs.map(o => o.path),};await Bun.write(manifestPath, JSON.stringify(manifest, null, 2));// IMPORTANT: Register custom files to prevent cleanup deletionresult.outputs.push({path: manifestPath,kind: "asset",hash: "",loader: "file",} as Bun.BuildArtifact);// Copy static assetsawait copyStaticAssets(config.outdir);},// Enable build loggingenableLoging: true,}
Output Cleanup Warning
Frame-Master automatically cleans up the output directory after each build. Any files not in result.outputs will be deleted. Always push custom files to the outputs array!
👁️ File System Hooks
Hooks for reacting to file system changes in development mode.
fileSystemWatchDir
fileSystemWatchDirstring[]Array of directory paths to watch for changes. Only active in development mode.
export function myPlugin(): FrameMasterPlugin {return {name: "my-plugin",// Specify directories to watchfileSystemWatchDir: ["src/","public/styles/","config/",],// ...};}
onFileSystemChange
onFileSystemChange(eventType: string, filePath: string, absolutePath: string) => Promise<void>Called when a file in watched directories changes. Only active in development mode.
Parameters:
eventType- Type of change ("change", "rename", etc.)filePath- Relative path to the changed fileabsolutePath- Absolute path to the changed file
onFileSystemChange: async (eventType, filePath, absolutePath) => {console.log(`File ${eventType}: ${filePath}`);// Rebuild CSS on style changesif (filePath.endsWith(".css")) {await rebuildStyles();console.log("Styles rebuilt");}// Clear cache on component changesif (filePath.includes("/components/")) {clearComponentCache();console.log("Component cache cleared");}// Reload config on config changesif (filePath.includes("config/")) {await reloadConfig();console.log("Config reloaded");}}
⚙️ Plugin Configuration
Configure plugin metadata, priority, and requirements.
name & version
namestringrequiredUnique identifier for the plugin. Used in logging and error messages.
versionstringrequiredSemantic version of the plugin (e.g., '1.0.0'). Required for dependency checking.
export function myPlugin(): FrameMasterPlugin {return {name: "my-custom-plugin",version: "1.0.0",// ...};}
priority
prioritynumberExecution priority. Lower numbers run first. Default is 50.
Default: 50
// Auth plugin - runs firstexport function authPlugin(): FrameMasterPlugin {return {name: "auth-plugin",priority: 0,// ...};}// Logging plugin - runs lastexport function loggingPlugin(): FrameMasterPlugin {return {name: "logging-plugin",priority: 100,// ...};}
requirement
requirement{ frameMasterVersion?, bunVersion?, frameMasterPlugins? }Specify version requirements for Frame-Master, Bun, and other plugins.
requirement: {// Require Frame-Master versionframeMasterVersion: "^1.0.0",// Require Bun runtime versionbunVersion: ">=1.2.0",// Require other pluginsframeMasterPlugins: {"frame-master-plugin-react-ssr": "^1.0.0","my-database-plugin": "^2.0.0",},}
📡 WebSocket Hooks
Hooks for real-time WebSocket communication.
serverConfig.routes
serverConfig.routesRecord<string, (req, server) => Response | undefined>Define WebSocket upgrade routes. Returns undefined to upgrade the connection.
serverConfig: {routes: {"/ws/my-plugin": (req, server) => {// Upgrade the connection with custom datareturn server.upgrade(req, {data: { "my-plugin-ws": true, userId: req.headers.get("x-user-id") },});},},},
websocket.onOpen
websocket.onOpen(ws: Bun.ServerWebSocket) => Promise<void> | voidCalled when a WebSocket connection is established.
websocket: {onOpen: async (ws) => {// Check if this connection belongs to your pluginif (!ws.data["my-plugin-ws"]) return;console.log("Client connected:", ws.data.userId);ws.send(JSON.stringify({ type: "connected", timestamp: Date.now() }));},},
websocket.onMessage
websocket.onMessage(ws: Bun.ServerWebSocket, message: string | ArrayBufferView) => Promise<void> | voidCalled when a message is received from the WebSocket client.
websocket: {onMessage: async (ws, message) => {if (!ws.data["my-plugin-ws"]) return;const data = JSON.parse(message.toString());switch (data.type) {case "ping":ws.send(JSON.stringify({ type: "pong" }));break;case "broadcast":// Handle broadcast logicbreak;}},},
websocket.onClose
websocket.onClose(ws: Bun.ServerWebSocket) => Promise<void> | voidCalled when the WebSocket connection is closed.
websocket: {onClose: async (ws) => {if (!ws.data["my-plugin-ws"]) return;console.log("Client disconnected:", ws.data.userId);// Cleanup resources, remove from rooms, etc.},},
Shared WebSocket Handlers
WebSocket handlers receive connections from all plugins. Use ws.data to identify which plugin the connection belongs to by checking the data set during the upgrade.
🔧 Advanced Features
Advanced plugin capabilities for custom functionality.
serverConfig
serverConfigPartial<Bun.Serve.Options>Customize Bun server options (excluding fetch, port, tls).
serverConfig: {// Custom routes for WebSocket upgrades or special handlingroutes: {"/ws/chat": (req, server) => server.upgrade(req, { data: { room: "chat" } }),"/health": () => new Response("OK"),},// Other Bun.serve optionsmaxRequestBodySize: 1024 * 1024 * 10, // 10MB},
cli
cli(command: Command) => CommandExtend Frame-Master CLI with custom commands using Commander.js.
cli: (command) => {return command.command("deploy").description("Deploy your application").option("-e, --env <environment>", "Target environment", "production").option("--dry-run", "Preview without deploying").action(async (options) => {console.log(`Deploying to ${options.env}...`);if (options.dryRun) {console.log("Dry run - no changes made");return;}// Your deployment logicawait deployToEnvironment(options.env);});}
CLI Access
Custom commands are accessible via frame-master extended-cli <your-command>. See Commander.js docs for full API.
directives
directivesArray<{ name: string, regex: RegExp }>Define custom directives for special file handling.
directives: [{name: "use-server",regex: /^(?:\s*(?:\/\/.*?\n|\s)*)?['""]use[-\s]server['""];?\s*(?:\/\/.*)?(?:\r?\n|$)/m,},{name: "use-client",regex: /^(?:\s*(?:\/\/.*?\n|\s)*)?['""]use[-\s]client['""];?\s*(?:\/\/.*)?(?:\r?\n|$)/m,},{name: "use-cache",regex: /^(?:\s*(?:\/\/.*?\n|\s)*)?['""]use[-\s]cache['""];?\s*(?:\/\/.*)?(?:\r?\n|$)/m,},]
runtimePlugins
runtimePluginsBunPlugin[]Bun runtime plugins for custom module resolution and transformation.
import type { BunPlugin } from "bun";const customLoader: BunPlugin = {name: "custom-loader",setup(build) {// Handle .custom filesbuild.onLoad({ filter: /\.custom$/ }, async (args) => {const contents = await Bun.file(args.path).text();return {contents: transformCustomFile(contents),loader: "js",};});// Resolve custom importsbuild.onResolve({ filter: /^@custom\/.*/ }, (args) => {return {path: resolveCustomPath(args.path),namespace: "custom",};});},};export function myPlugin(): FrameMasterPlugin {return {name: "my-plugin",runtimePlugins: [customLoader],// ...};}
🔄 Plugin Lifecycle
Hooks execute in priority order (lower numbers first).
Priority Order
Within each hook, plugins execute in priority order. A plugin with priority: 0 runs before one with priority: 50. Plugins without a priority default to 50.
