Captcha

Captcha

Protect registration, password reset, and custom API routes against bots with Cloudflare Turnstile or Google reCAPTCHA v3.

VitNode provides built-in bot protection. When enabled in your API configuration, gated routes automatically demand and verify a challenge token before handler execution.

Quick start

1. Configure Captcha in API Config

Add the captcha configuration to apps/api/src/vitnode.api.config.ts:

apps/api/src/vitnode.api.config.ts
import { buildApiConfig } from "@vitnode/core/vitnode.config"

export const vitNodeApiConfig = buildApiConfig({
  captcha: process.env.CLOUDFLARE_TURNSTILE_SECRET_KEY
    ? {
        type: "cloudflare_turnstile",
        siteKey: process.env.CLOUDFLARE_TURNSTILE_SITE_KEY,
        secretKey: process.env.CLOUDFLARE_TURNSTILE_SECRET_KEY,
      }
    : undefined,
})

Built-in user registration and password recovery routes are protected immediately:

Cloudflare Turnstile widget showing success checkmark on VitNode sign-up form

2. Protect Custom API Routes

Enable captcha verification on any custom Hono endpoint with withCaptcha: true:

plugins/blog/src/api/modules/comments/routes/create.route.ts
import { buildRoute } from "@vitnode/core/api/lib/route"

export const createCommentRoute = buildRoute({
  pluginId: "blog",
  withCaptcha: true, 
  route: {
    method: "post",
    path: "/",
    responses: { 201: { description: "Comment created" } },
  },
  handler: async (c) => {
    // Execution only reaches here if the captcha token is valid
  },
})

3. Render the Frontend Widget

Include <Captcha /> in your form and supply the received token to fetcherClient:

plugins/blog/src/views/comment-form.tsx
import { Captcha } from "@vitnode/core/components/captcha"
import { fetcherClient } from "@vitnode/core/lib/fetcher-client"

export const CommentForm = () => {
  const [captchaToken, setCaptchaToken] = React.useState<string>()

  const handleSubmit = async () => {
    await fetcherClient(commentsModule, {
      method: "post",
      path: "/",
      captchaToken, 
      body: { text: "Nice article!" },
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <textarea />
      <Captcha onVerify={setCaptchaToken} />
      <button disabled={!captchaToken}>Submit Comment</button>
    </form>
  )
}

Supported Providers

Learn More