Install
$ agentstack add skill-mateonunez-skills-fastify-plugin-shape ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Fastify plugin shape
> If it doesn't hold up in production, it doesn't make the cut.
I publish Fastify plugins (fastify-orama, fastify-at-mysql, lyra-impact). The shape below is what every one of mine looks like. It's small, it's deliberate, and it doesn't drift between repos.
When this skill is active
You are about to:
- Write a new Fastify plugin (in-repo or standalone package)
- Add a
fastify.decorate(...)call - Configure or modify a
fastify-plugin(fp) wrapper - Add a TypeScript module augmentation (
declare module 'fastify' {}) - Validate plugin options or decide what to expose
The shape
'use strict'
const fp = require('fastify-plugin')
async function fastifyMyPlugin (fastify, options) {
if (fastify.myPlugin) {
throw new Error('fastify-my-plugin is already registered')
}
const { /* my-plugin options */ ...rest } = options
// ... build api, open connections, restore state ...
function withMyPlugin () {
return this
}
fastify.decorate('myPlugin', api)
fastify.decorate('withMyPlugin', withMyPlugin)
}
module.exports = fp(fastifyMyPlugin, {
fastify: '5.x',
name: 'fastify-my-plugin'
})
// Re-export raw function + helpers as named exports
module.exports.fastifyMyPlugin = fastifyMyPlugin
That's the spine. Eight non-negotiables:
'use strict'at the top, CommonJS, JS source — TS lives inindex.d.ts.fastify-pluginwrapper. Without it, decorators are encapsulated and your plugin "doesn't work" outside its register scope.fpis the de-encapsulation primitive.async (fastify, options)signature. Even if you don't await — keep it async for symmetry.- Idempotency guard. First line of the body checks
if (fastify.myPlugin) throw. Stops the foot-gun where two registers silently clobber each other. - Destructure plugin-only options before forwarding. Your plugin's options are not the underlying library's options — separate them at the boundary, don't leak them.
withMyPlugin()helper that returnsthis. No-op at runtime; pure TypeScript narrowing tool. Lets users writefastify.withMyPlugin().myPlugin.foo()in handlers where the decorator's type isn't yet visible.module.exports = fp(plugin, { fastify, name })— pin the Fastify major ('5.x') and the plugin name. The name is what shows up infpwarnings.- Re-export named. Default-export the wrapped plugin, named-export the raw async function and any side classes (persistence adapters, helpers). Lets consumers compose.
TypeScript module augmentation
Ship types in index.d.ts. Augment Fastify, don't subclass it:
import 'fastify'
declare module 'fastify' {
interface FastifyInstance {
myPlugin: MyPluginApi
withMyPlugin: () => FastifyInstance & { myPlugin: MyPluginApi }
}
}
export interface MyPluginOptions { /* ... */ }
export interface MyPluginApi { /* ... */ }
declare const fastifyMyPlugin: FastifyPluginAsync
export default fastifyMyPlugin
Module augmentation is the only correct way — it composes with other plugins, it's discoverable, it works with Fastify's type inference.
Options validation
Validate at the entry point, fail fast:
if (!options.schema && !options.persistence) {
throw new Error('You must provide a schema or a persistence adapter')
}
Don't reach for ajv for plugin options — overkill. Plain checks at the top of the function are enough; the schema for request/response is what ajv is for.
Decorator naming
- Singular noun for the resource:
fastify.orama,fastify.mysql,fastify.redis. Notfastify.oramaClient, notfastify.dbConnection. with()helper for the type-narrowing trick. Always returnsthis.- No verbs as decorator names.
fastify.search()is wrong — that lives on the API object:fastify.orama.search().
Anti-patterns
- Plugin without
fastify-plugin. Decorators get encapsulated and disappear in handlers. The bug is silent and infuriating to debug. fastify.decorate('orama', () => api)— passing a function instead of the value. Now every callsite invokes it (fastify.orama().search). Pass the API object directly.- Leaking the underlying library's instance instead of an API surface. Don't
fastify.decorate('orama', oramaDb)and let consumers callOrama.search(fastify.orama, …). Wrap the methods so the db instance is implicit:fastify.orama.search(...). - No idempotency guard. Two
registercalls silently overwrite the decorator and you spend an afternoon hunting it down. - Pinning Fastify with
^5.0.0instead of'5.x'. Thefppeer constraint is a major-version range, not a npm range. - TS via subclassing or extension instead of module augmentation. Won't compose with other plugins.
- No
withX()helper. Users have to castfastify as FastifyInstance & { myPlugin: ... }everywhere. Annoying. The helper costs three lines.
When the plugin needs cleanup
Use fastify.addHook('onClose', async () => { /* close db, drain queue, persist state */ }). Don't expose a manual close() decorator — the framework already has the lifecycle hook.
Cross-references
- Real example: [
deprecated/fastify-orama/references/core-plugin-api.md](../../deprecated/fastify-orama/references/core-plugin-api.md) - Source: github.com/mateonunez/fastify-orama
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mateonunez
- Source: mateonunez/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.