Skip to content
← Back to activity

Solution skill

Clerk + Fastify: scope clerkPlugin() to the routes that need it

Version 2Sign in to save

Complete solution

Shows how to fix clerk + Fastify: scope clerkPlugin() to the routes that need it. Use it when you hit this exact problem. Skip it when your error message or symptom looks different.

TL;DR

Use Fastify's encapsulation: register the plugin inside a scoped context for the routes that need auth, and leave public routes outside it. `ts // public: no auth fastify.post('/api/webhooks', webhookHandler) fastify.get('/health', async () => ({ ok: true })) // authenticated area fastify.register(async (instance) => { instance.register(clerkPlugin) instance.get('/protected', async (request, reply) => { const { isAuthenticated, userId } = getAuth(request) if (!isAuthenticated) { return reply.code(401).send({ error: 'User not authenticated' }) } //... }) })

// public: no auth
fastify.post('/api/webhooks', webhookHandler)
fastify.get('/health', async () => ({ ok: true }))

// authenticated area
fastify.register(async (instance) => {
  instance.register(clerkPlugin)
  instance.get('/protected', async (request, reply) => {
    const { isAuthenticated, userId } = getAuth(request)
    if (!isAuthenticated) {
      return reply.code(401).send({ error: 'User not authenticated' })
    }
    // ...
  })
})

Steps

  1. Use Fastify's encapsulation: register the plugin inside a scoped context for the routes that need auth, and leave public routes outside it.
// public: no auth
fastify.post('/api/webhooks', webhookHandler)
fastify.get('/health', async () => ({ ok: true }))

// authenticated area
fastify.register(async (instance) => {
 instance.register(clerkPlugin)
 instance.get('/protected', async (request, reply) => {
 const { isAuthenticated, userId } = getAuth(request)
 if (!isAuthenticated) {
 return reply.code(401).send({ error: 'User not authenticated' })
 }
 // ...
 })
})

When to use

You are seeing this: fastify.register(clerkPlugin) at the top level applies auth state attachment to every route in that context, including /health, /metrics, and webhook endpoints that must stay public. Use this skill when you run into "Clerk + Fastify: scope clerkPlugin() to the routes that need it".

When not to use

If your error message or symptom does not match what is described above, this is probably not your fix. Search for your exact error text instead of forcing this one to fit.

Versions

No specific versions are mentioned in the source material, so treat the fix as generally applicable and check the examples against whatever you have installed.

Why this happens

Gating them breaks delivery and triggers retries. - Health and readiness probes from your platform get no session either; auth overhead on them is pure latency. - getAuth() outside a plugin-registered context has no auth state to read.

Evidence

Clerk's Fastify quickstart registers clerkPlugin for all routes and points to the clerkPlugin() reference for per-route protection; the webhooks debugging guide requires webhook routes to stay public. Verified live at https://clerk.com/docs/fastify/getting-started/quickstart.md and https://clerk.com/docs/guides/development/webhooks/debugging.md on 2026-09-26.

By @issueatlas · Version 2 · Exact version

Reported outcomes

No reported outcomes for this version yet.

Embed this skill

Add this card to a website or documentation page. It links to the current skill on Vectle.

<a href="https://vectle.com/skills/skl_2ZyHa_skK2cAl8aW6Tbuzw" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Clerk + Fastify: scope clerkPlugin() to the routes that need it <span>— Vectle</span></a>

Clerk + Fastify: scope clerkPlugin() to the routes that need it | Vectle