Plugins
The plugin API lets packages extend Clear Router's behaviour in a structured way. Plugins can register container bindings, augment the HTTP context, replace controller method arguments, configure the router, and access the active request and response.
import { definePlugin } from 'clear-router/core';
export const myPlugin = definePlugin({
name: 'my-plugin',
setup({ bind, useHttpContext, resolveArguments }) {
// ...
},
});Apps install plugins on any router adapter via Router.use():
import Router from 'clear-router/express';
import { myPlugin } from './my-plugin';
Router.use(myPlugin);Named plugins are installed once per process. Calling Router.use() with the same plugin name more than once has no effect.
Defining a Plugin
definePlugin(options)
Creates a plugin definition. The setup function receives a ClearRouterPluginContext with everything the plugin needs to interact with the router.
export const auditPlugin = definePlugin({
name: 'audit',
setup({ bind }) {
bind(AuditService, () => new AuditService());
},
});Accepting Options
Plugins can declare a typed options interface as the first generic argument to definePlugin. Options are passed as the second argument to Router.use().
export const auditPlugin = definePlugin<{ name: string }>({
name: 'audit',
setup({ bind, options }) {
bind(AuditService, () => new AuditService(options.name));
},
});
Router.use(auditPlugin, { name: 'app' });Typing the HTTP Context
The second generic argument to definePlugin is the HttpContext type — the framework-specific context object passed to useHttpContext and resolveArguments. Without it, the context defaults to any. Pass your driver's context type to get full type safety inside those resolvers.
import type { H3Event } from 'h3';
export const myPlugin = definePlugin<{ name: string }, H3Event>({
name: 'my-plugin',
setup({ useHttpContext }) {
useHttpContext(({ ctx }) => {
// ctx is typed as H3Event
ctx.context.pluginName = 'my-plugin';
});
},
});The same HttpContext type flows through to resolveArguments:
setup ({ resolveArguments }) {
resolveArguments(({ ctx, method, request }) => {
// ctx is typed as H3Event
if (method !== 'show') return undefined
return [new BoundUser(request.param('id'), ctx.method)]
})
}Async Setup
setup can be async. Clear Router waits for all async plugins to complete before resolving container bindings for any request.
export const auditPlugin = definePlugin({
name: 'audit',
async setup({ bind }) {
await someAsyncInitialisation();
bind(AuditService, () => new AuditService());
},
});Plugin Context
The setup function receives a ClearRouterPluginContext with the following members:
bind(token, factory)
Registers a service with the container. The factory receives a dependency object containing the active request and response when the binding is resolved during a request.
setup ({ bind }) {
bind(AuditService, (d: { request: ClearRouterRequest }) => {
return new AuditService(d.request.param('id'), d.request.method)
})
}Requires
container.enabled: trueinRouter.configure().
useHttpContext(resolver)
Runs a callback on every request, giving the plugin access to the active HTTP context. Use this to attach data to ctx before it reaches a controller. The ctx type is inferred from the HttpContext generic on definePlugin — see Typing the HTTP Context.
setup ({ useHttpContext }) {
useHttpContext(({ ctx }) => {
ctx.pluginName = 'my-plugin'
})
}The augmented values are then available as controller method arguments:
class UsersController {
show(ctx: { pluginName: string }) {
return { pluginName: ctx.pluginName };
}
}resolveArguments(resolver)
Replaces the arguments passed to a controller method entirely. The resolver receives { ctx, method, request } and should return an array of arguments, or undefined to fall through to the default resolution. Like useHttpContext, the ctx type is inferred from the HttpContext generic on definePlugin.
Returning an empty array ([]) also falls back to the default handler signature.
setup ({ resolveArguments }) {
resolveArguments(({ method, request }) => {
if (method !== 'show') return undefined
return [
new BoundUser(request.param('id'), 'plugin'),
request,
{ touched: true },
]
})
}configure(options) / configureDefaults(options)
Configures the router from within the plugin. configure applies options for the current state; configureDefaults sets options that are restored every time the router is reset.
setup ({ configure }) {
configure({ methodOverride: { enabled: true } })
}getRequest() / getResponse()
Returns the active CoreRequest or CoreResponse instance. Both return undefined outside of a request lifecycle — for example, during plugin setup.
setup ({ getRequest }) {
// getRequest() is undefined here — no active request yet
bind(AuditService, () => {
const request = getRequest()
return new AuditService(request?.method ?? 'unknown')
})
}container
Direct access to the service container.
bindings
A record of all currently registered container bindings.
options
The options object passed to Router.use(). Only present when the plugin declares a typed options interface via definePlugin<Options>().
Full Example
import { definePlugin } from 'clear-router/core';
import { ClearRouterRequest } from 'clear-router/express';
class RequestAuditService {
constructor(
readonly id: string,
readonly method: string,
) {}
}
export const requestAuditPlugin = definePlugin({
name: 'request-audit',
setup({ bind }) {
bind(RequestAuditService, (d: { request: ClearRouterRequest }) => {
return new RequestAuditService(d.request.param('id'), d.request.method);
});
},
});// router setup
Router.configure({ container: { enabled: true } });
Router.use(requestAuditPlugin);
// controller
class UsersController {
@Bind(RequestAuditService)
show(audit: RequestAuditService) {
return { id: audit.id, method: audit.method };
}
}