When You WANT to Cross the Encapsulation Boundary
The fastify-plugin Wrapper
Wrap your plugin with fastify-plugin to expose its decorators and hooks to the parent context instead of keeping them encapsulated.
What you'll learn
- Install fastify-plugin
- Wrap a plugin with fp(...)
- Know which infrastructure plugins need it
By default, every Fastify plugin lives in its own scope — decorators and hooks added inside it
are invisible to siblings. That isolation is great for routes but inconvenient for shared
infrastructure. fastify-plugin opts out.
Install
npm i fastify-plugin Use fp for Infrastructure
import fp from 'fastify-plugin';
import type { FastifyPluginAsync } from 'fastify';
const dbPlugin: FastifyPluginAsync = async (app) => {
const client = await createDbClient(app.config.DB_URL);
app.decorate('db', client);
app.addHook('onClose', async () => client.close());
};
export default fp(dbPlugin, {
name: 'db',
fastify: '5.x',
}); Because of fp, the app.db decorator is reachable from sibling plugins — your routes can now
write app.db.query(...) regardless of where they live in the tree.
When NOT to Use fp
Route plugins should stay encapsulated. If usersRoutes and billingRoutes register their own
preHandler hooks, those hooks belong to each subtree only — wrapping them with fp would make
them fire on every route in the app.
Rule of thumb: wrap with fp when the plugin exposes a service (DB client, auth helper, mailer).
Skip fp when the plugin defines routes or per-feature middleware.
Naming and Dependencies
export default fp(authPlugin, {
name: 'auth',
dependencies: ['db'],
}); If you forget to register db first, Fastify throws a clear error at boot — much better than a
runtime null reference deep in a handler.