Hooks and Plugins
The BurgerAPI ecosystem ships two kinds of packages, plus AI agent skills. See Ecosystem for the folder layout and CLI commands.
Hooks
Hooks are small request lifecycle factories. They control when code runs on a request.
- What they do: run at a named lifecycle stage (
onRequest,transform,beforeRoute,afterRoute,mapResponse,onError). - Where they live:
ecosystem/hooks/after install. - Where you compose them:
src/hooks.ts(global) or a route'shooks.ts(route-specific). - Examples: cors, logger, rate-limiter, compression, timeout, security-headers.
import { cors } from "../ecosystem/hooks/cors/cors";
export const onRequest = [cors({ origin: ["https://app.example.com"] })];
Plugins
Plugins are application extensions. They package hooks and inject request-scoped values onto BurgerContext (for example ctx.apiKey from transform, or ctx.user from beforeRoute). They cannot register providers: services are registered in src/providers.ts with burger.provide().
- What they do: integrate with the application, often combining hooks with route
config.ts(for example, auth). - Where they live:
ecosystem/plugins/after install. - Where you register them:
src/plugins.ts. - Examples: jwt-auth, api-key, session, oidc, basic-auth, env.
import type { PluginRegistrar } from "burger-api";
import { apiKey } from "../ecosystem/plugins/api-key/api-key";
export default (burger: PluginRegistrar) => {
burger.usePlugin(apiKey({ keys: ["demo-api-key-123"] }));
};
usePlugin() accepts a plugin object or a factory function, plus two optional arguments:
usePlugin(plugin: Plugin | PluginFactory, scope?: Scope, seed?: string): this
scope overrides where the plugin's hooks sit in the chain (default "plugin"). seed disambiguates two registrations of the same plugin, for example two JWT plugins with different secrets.
Which one to use?
- Need code to run around requests, like adding headers or logging? Use a hook.
- Need an application feature like authentication or environment validation? Use a plugin.
Hooks control the request lifecycle. Plugins extend the application. They are separate concepts. Do not treat plugins as a replacement for hooks.
Types for this feature
The types you use (all from burger-api):
Pluginis a plugin:{ name, hooks? }PluginFactoryis a function returning a plugin (or a promise of one)GlobalHooksis the hook object a plugin usesRouteHooksis the hook object (for typinghooks.tsfiles)BurgerContextis augmented to type values injected bytransform
✅ Correct: type the plugin when you write one:
import type { Plugin, PluginRegistrar } from "burger-api";
const logger: Plugin = {
name: "logger",
hooks: {
afterRoute: [(ctx) => (res) => res],
},
};
export default (burger: PluginRegistrar) => {
burger.usePlugin(logger);
};
✅ Correct: inject a typed value with transform (augment BurgerContext in any app file):
import type { Plugin } from "burger-api";
declare module "burger-api" {
interface BurgerContext {
tenant: string;
}
}
const tenant: Plugin = {
name: "tenant",
hooks: {
transform: {
tenant: (ctx) => ctx.headers.get("x-tenant") ?? "public",
},
},
};
// In a handler: ctx.tenant (typed)
❌ Wrong: an unknown hook point on a plugin:
const bad: Plugin = {
name: "bad",
hooks: {
// ❌ Property 'beforeHandle' does not exist (the hook is beforeRoute)
beforeHandle: [],
},
};
See the TypeScript overview.
Check your code: bun run typecheck.