The fastify-plugin Wrapper

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.

4 min read Level 2/5 #fastify#plugins#fastify-plugin
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.

Encapsulation — The Big Idea →