For AI agents: the complete documentation index is available at https://dotpress.dev/llms.txt, the full documentation bundle is available at https://dotpress.dev/llms-full.txt, and this page is available as Markdown at https://dotpress.dev/reference/plugins.md.

Plugin API

definePlugin(), the API given to setup(), and the hooks. For a guide, see Plugins.

export const 
const metricsPlugin: (metrics: Metrics) => {
    readonly name: "metrics";
    readonly setup: (api: PluginAPI) => void;
}
metricsPlugin
= (
metrics: Metrics
metrics
: Metrics) =>
definePlugin<{
    readonly name: "metrics";
    readonly setup: (api: PluginAPI) => void;
}>(plugin: {
    readonly name: "metrics";
    readonly setup: (api: PluginAPI) => void;
}): {
    readonly name: "metrics";
    readonly setup: (api: PluginAPI) => void;
}

Declares a plugin. Extra properties are allowed, and exposed to other plugins through api.getPlugin().

@exampleexport const healthPlugin = (path = '/health') => definePlugin({ name: 'health', setup(api) { api.addRoute({ path, handler: () => ({ status: 'ok' }) }) }, })
definePlugin
({
name: "metrics"
name
: 'metrics',
function setup(api: PluginAPI): void
setup
(
api: PluginAPI
api
) {
api: PluginAPI
api
.
PluginAPI.hooks: PluginHooks
hooks
.
PluginHooks.onResponse(hook: ResponseHook): void
onResponse
(({
route: RouteInfo | undefined

Undefined for requests that matched no route.

route
,
response: NormalizedResponse
response
,
duration: number

Time spent producing the response, in milliseconds. Streamed bodies are not included.

duration
}) => {
metrics: Metrics
metrics
.
Metrics.observe(name: string, value: number, labels: Record<string, string>): void
observe
('http_request_duration_ms',
duration: number

Time spent producing the response, in milliseconds. Streamed bodies are not included.

duration
, {
route: string
route
:
route: RouteInfo | undefined

Undefined for requests that matched no route.

route
? `${
route: RouteInfo

Undefined for requests that matched no route.

route
.
RouteInfo.method: HttpMethod
method
.
String.toUpperCase(): string

Converts all the alphabetic characters in a string to uppercase.

toUpperCase
()} ${
route: RouteInfo

Undefined for requests that matched no route.

route
.
RouteInfo.path: string

Path in Dotpress syntax (/users/:id).

path
}` : 'unmatched',
status: string
status
:
var String: StringConstructor
(value?: any) => string

Allows manipulation and formatting of text strings and determination and location of substrings within strings.

String
(
response: NormalizedResponse
response
.
NormalizedResponse.status: number
status
),
}) }) }, })

definePlugin()

function definePlugin<T extends Plugin>(plugin: T): T

interface Plugin {
  readonly name: string
  readonly dependsOn?: readonly string[]
  setup(api: PluginAPI): void | Promise<void>
}
MemberDescription
nameUnique in the app. Used by dependsOn and api.getPlugin().
dependsOnNames of plugins set up before this one. A missing one makes createApp() fail.
setup(api)Registers the features of the plugin. Called once, in dependency order. Can be async.

Extra properties are allowed, and readable by other plugins with api.getPlugin(). By convention, a plugin is exported as a function that takes its options: cachePlugin({ cache }).

The plugins of Dotpress are named auth, cache, events, mailer and openapi: dependsOn: ['auth'].

PluginAPI

The registration methods work during setup() only: called later, they throw.

MemberDescription
addRoute(config)Declares a route, like defineRoute().
addRouteGroup(prefix, middlewares?)Creates a group, like createRouteGroup().
addGlobalMiddleware(middleware)A middleware run on every route, before those of createApp().
addResponseFilter(filter)A filter applied to every JSON success, after those of createApp().
decorateContext(key, factory)Provides ctx[key]: see Context keys.
onError(hook)Called with unexpected errors, like the onError option.
hooksThe lifecycle and request hooks.
nameThe name of the plugin.
loggerA logger bound to the plugin (plugin field).
basePath, publicBasePathThe options of the app, normalized, or ''.
publicPath(path)The path seen by the browser: for a cookie Path or a documentation URL.
corsThe CORS settings: credentials, and isOriginAllowed(req). undefined without CORS.
getRoutes()The mounted routes (RouteInfo[]). From the onInit hook, or at request time.
getPlugin(name)Another plugin, or undefined.
getPlugins()Every plugin, in setup order.

Hooks

HookSignatureWhen
onInit() => void | Promise<void>Every plugin is set up and the routes are compiled. getRoutes() is available.
onReady() => void | Promise<void>The routes are mounted on the adapter, before listen().
wrapRequest(ctx, next) => Promise<T>Runs the rest of the request inside the hook: must call next() and return its result.
onRequest(ctx) => MiddlewareResultEach request matching a route, before the middlewares. Returning an error or a response stops it.
onResponse(event) => void | NormalizedResponseEach response, 404, 405 and adapter errors included. Returning a response replaces it.
onClose() => void | Promise<void>app.close(), after the server is stopped. Plugins set up last close first.

onInit and onReady run inside createApp(): a failure makes it reject. The hooks of several plugins run in setup order; for wrapRequest, the first plugin is the outermost.

The response event

FieldDescription
requestThe normalized request.
responseThe normalized response: status, headers, body, kind.
requestIdThe ID of the request.
ctxThe context, undefined when no route matched.
routeThe route (RouteInfo), undefined when no route matched.
durationTime spent producing the response, in milliseconds, without the streamed body.

Order in a request

  1. CORS preflight requests are answered.
  2. No route: 404 or 405, then onResponse.
  3. The request ID and the request logger are created.
  4. wrapRequest, then onRequest.
  5. The middlewares: plugins, createApp(), groups, route.
  6. Request validation, the handler, response validation.
  7. The response filters: createApp(), plugins, router.
  8. CORS headers, then onResponse.

See The request pipeline.

Context keys

declare module '@dotpress/core' {
  interface RequestContext {
    tenant: TenantService
  }
}

declareContextKey('tenant', 'tenantPlugin')

export const tenantPlugin = (tenants: TenantService) =>
  definePlugin({
    name: 'tenant',
    setup(api) {
      api.decorateContext('tenant', (ctx) => tenants.withLogger(ctx.logger))
    },
  })
  • api.decorateContext(key, factory): the factory receives the context, and runs on first access, at most once per request. Two plugins cannot provide the same key, and the keys of the core (req, res, requestId, logger, raw, publicPath) are reserved.
  • declareContextKey(key, pluginName): at the top level of the module, next to the augmentation. Without the plugin, ctx.tenant throws "ctx.tenant" is used, but the "tenantPlugin" plugin is not installed in createApp().

Plugin or middleware?

A middleware is enough for logic attached to some routes. Write a plugin when the feature needs routes of its own, a context key, a hook on every response, or code at startup and shutdown. See Plugin or middleware?.