Documentation

Plugin Hooks Reference

Complete reference guide for all available hooks in Frame-Master plugins.

🚀 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.

server-start-main.ts
export function myPlugin(): FrameMasterPlugin {
return {
name: "my-plugin",
serverStart: {
main: async () => {
// Initialize database connections
await db.connect();
// Load configuration
const config = await loadConfig();
// Set up global state
global.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.

server-start-dev.ts
serverStart: {
dev_main: async () => {
// Enable debug logging
enableDebugMode();
// Start file watcher
watchForChanges();
// Initialize hot reload
setupHotReload();
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 data
  • master.setGlobalValues(values) - Inject global values accessible in client code
  • master.request - Access the incoming Request object
before-request.ts
router: {
before_request: async (master) => {
// Initialize context
master.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 object
  • master.setResponse(body, options) - Set the response body and options
  • master.sendNow() - Send response immediately, skipping other plugins
  • master.getContext() - Get request context data
request.ts
router: {
request: async (master) => {
const url = new URL(master.request.url);
// Handle API routes
if (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 plugins
return;
}
// 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 object
  • master.getContext() - Get request context data
after-request.ts
router: {
after_request: async (master) => {
const response = master.response;
if (!response) return;
// Add security headers
response.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 information
const context = master.getContext();
const duration = Date.now() - context.startTime;
response.headers.set("X-Response-Time", `${duration}ms`);
// Log request
console.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.

html-rewrite.ts
router: {
html_rewrite: {
// Initialize context for HTML rewriting
initContext: (req) => {
return {
injectAnalytics: process.env.NODE_ENV === "production",
theme: req.headers.get("x-theme") || "dark",
userId: req.headers.get("x-user-id"),
};
},
// Rewrite HTML elements
rewrite: async (reWriter, master, context) => {
// Inject scripts in head
reWriter.on("head", {
element(element) {
if (context.injectAnalytics) {
element.append(
'<script src="/analytics.js"></script>',
{ html: true }
);
}
},
});
// Modify body attributes
reWriter.on("body", {
element(element) {
element.setAttribute("data-theme", context.theme);
if (context.userId) {
element.setAttribute("data-user", context.userId);
}
},
});
// Transform specific elements
reWriter.on("img", {
element(element) {
const src = element.getAttribute("src");
if (src && !src.startsWith("http")) {
element.setAttribute("loading", "lazy");
}
},
});
},
// Final processing after HTML rewrite
after: async (HTML, master, context) => {
// Additional HTML transformations
console.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.

👁️ 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.

watch-dirs.ts
export function myPlugin(): FrameMasterPlugin {
return {
name: "my-plugin",
// Specify directories to watch
fileSystemWatchDir: [
"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 file
  • absolutePath - Absolute path to the changed file
on-change.ts
onFileSystemChange: async (eventType, filePath, absolutePath) => {
console.log(`File ${eventType}: ${filePath}`);
// Rebuild CSS on style changes
if (filePath.endsWith(".css")) {
await rebuildStyles();
console.log("Styles rebuilt");
}
// Clear cache on component changes
if (filePath.includes("/components/")) {
clearComponentCache();
console.log("Component cache cleared");
}
// Reload config on config changes
if (filePath.includes("config/")) {
await reloadConfig();
console.log("Config reloaded");
}
}

⚙️ Plugin Configuration

Configure plugin metadata, priority, and requirements.

name

namestringrequired

Unique identifier for the plugin. Used in logging and error messages.

name.ts
export function myPlugin(): FrameMasterPlugin {
return {
name: "my-custom-plugin",
// ...
};
}

priority

prioritynumber

Execution priority. Lower numbers run first. Default is 50.

Default: 50

priority.ts
// Auth plugin - runs first
export function authPlugin(): FrameMasterPlugin {
return {
name: "auth-plugin",
priority: 0,
// ...
};
}
// Logging plugin - runs last
export function loggingPlugin(): FrameMasterPlugin {
return {
name: "logging-plugin",
priority: 100,
// ...
};
}

requirement

requirement{ frameMasterVersion?, bunVersion?, frameMasterPlugins? }

Specify version requirements for Frame-Master, Bun, and other plugins.

requirements.ts
requirement: {
// Require Frame-Master version
frameMasterVersion: "^1.0.0",
// Require Bun runtime version
bunVersion: ">=1.2.0",
// Require other plugins
frameMasterPlugins: {
"frame-master-plugin-react-ssr": "^1.0.0",
"my-database-plugin": "^2.0.0",
},
}

🔧 Advanced Features

Advanced plugin capabilities for custom functionality.

directives

directivesArray<{ name: string, regex: RegExp }>

Define custom directives for special file handling.

directives.ts
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.

runtime-plugins.ts
import type { BunPlugin } from "bun";
const customLoader: BunPlugin = {
name: "custom-loader",
setup(build) {
// Handle .custom files
build.onLoad({ filter: /\.custom$/ }, async (args) => {
const contents = await Bun.file(args.path).text();
return {
contents: transformCustomFile(contents),
loader: "js",
};
});
// Resolve custom imports
build.onResolve({ filter: /^@custom\/.*/ }, (args) => {
return {
path: resolveCustomPath(args.path),
namespace: "custom",
};
});
},
};
export function myPlugin(): FrameMasterPlugin {
return {
name: "my-plugin",
runtimePlugins: [customLoader],
// ...
};
}

📊 Hook Execution Order

Complete Hook Execution Flow

// Server Startup
1. Load all plugins
2. Sort by priority
3. Validate requirements
4. Execute serverStart.main (by priority)
5. Execute serverStart.dev_main (by priority, dev only)
6. Load runtimePlugins
7. Start file watchers (dev only)
// Per Request
1. router.before_request (by priority)
2. router.request (by priority, until sendNow())
3. Process route/page
4. router.after_request (by priority)
5. router.html_rewrite (HTML responses only)
6. Send response
// File Change (dev only)
→ onFileSystemChange (by priority)

🎯Next Steps