# Captcha (/docs/plugins/captcha)

Captcha plugin



The **Captcha Plugin** integrates bot protection into your Better Auth system by adding captcha verification for key endpoints. This plugin ensures that only human users can perform actions like signing up, signing in, or resetting passwords. The following providers are currently supported:

* [Google reCAPTCHA](https://developers.google.com/recaptcha)
* [Cloudflare Turnstile](https://www.cloudflare.com/application-services/products/turnstile/)
* [hCaptcha](https://www.hcaptcha.com/)
* [CaptchaFox](https://captchafox.com/)
* [Vercel BotID](https://vercel.com/docs/botid)

<Callout type="info">
  The default endpoints cover <Link href="/docs/authentication/email-password">Email & Password</Link> authentication. Other authentication methods need an explicit <Link href="/docs/plugins/captcha#plugin-options">endpoints</Link> array. Vercel BotID also needs the client-side setup below.
</Callout>

## Installation [#installation]

<Steps>
  <Step>
    ### Install dependencies (Optional) [#install-dependencies-optional]

    If you are using **Vercel BotID**, install its SDK in your application:

    <CodeBlockTabs defaultValue="npm" groupId="persist-install">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="npm">
          npm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="pnpm">
          pnpm
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="yarn">
          yarn
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="bun">
          bun
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="npm">
        ```bash
        npm install botid
        ```
      </CodeBlockTab>

      <CodeBlockTab value="pnpm">
        ```bash
        pnpm add botid
        ```
      </CodeBlockTab>

      <CodeBlockTab value="yarn">
        ```bash
        yarn add botid
        ```
      </CodeBlockTab>

      <CodeBlockTab value="bun">
        ```bash
        bun add botid
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Step>

  <Step>
    ### Add the plugin to your **auth** config [#add-the-plugin-to-your-auth-config]

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { captcha } from "better-auth/plugins"; // [!code highlight]

    export const auth = betterAuth({
        plugins: [
            captcha({ // [!code highlight]
                provider: "cloudflare-turnstile", // or google-recaptcha, hcaptcha, captchafox // [!code highlight]
                secretKey: process.env.TURNSTILE_SECRET_KEY!, // [!code highlight]
            }), // [!code highlight]
        ],
    });
    ```
  </Step>

  <Step>
    ### Send the token for token-based providers [#send-the-token-for-token-based-providers]

    <Callout type="warning">
      The `x-captcha-user-remote-ip` header is no longer required—IP is now auto-detected server-side.
    </Callout>

    For token-based providers, add the captcha token to requests for protected endpoints. This example shows a `signIn` request:

    ```ts
    import { authClient } from "@/lib/auth-client"

    await authClient.signIn.email({
        email: "user@example.com",
        password: "secure-password",
        fetchOptions: { // [!code highlight]
            headers: { // [!code highlight]
                "x-captcha-response": turnstileToken, // [!code highlight]
            }, // [!code highlight]
        }, // [!code highlight]
    });
    ```

    * To implement Cloudflare Turnstile on the client side, follow the official [Cloudflare Turnstile documentation](https://developers.cloudflare.com/turnstile/) or use a library like [react-turnstile](https://www.npmjs.com/package/@marsidev/react-turnstile).
    * To implement Google reCAPTCHA on the client side, follow the official [Google reCAPTCHA documentation](https://developers.google.com/recaptcha/intro) or use libraries like [react-google-recaptcha](https://www.npmjs.com/package/react-google-recaptcha) (v2) and [react-google-recaptcha-v3](https://www.npmjs.com/package/react-google-recaptcha-v3) (v3).
    * To implement hCaptcha on the client side, follow the official [hCaptcha documentation](https://docs.hcaptcha.com/#add-the-hcaptcha-widget-to-your-webpage) or use libraries like [@hcaptcha/react-hcaptcha](https://www.npmjs.com/package/@hcaptcha/react-hcaptcha)
    * To implement CaptchaFox on the client side, follow the official [CaptchaFox documentation](https://docs.captchafox.com/getting-started) or use libraries like [@captchafox/react](https://www.npmjs.com/package/@captchafox/react)
  </Step>
</Steps>

## Providers [#providers]

### Vercel BotID [#vercel-botid]

BotID requires a Vercel deployment and client-side setup. Follow [Vercel's setup guide](https://vercel.com/docs/botid/get-started) for the rewrites and client integration appropriate to your framework. The Better Auth plugin does not use a `secretKey` or an `x-captcha-response` header:

```ts title="auth.ts"
import { checkBotId } from "botid/server";
import { betterAuth } from "better-auth";
import { captcha } from "better-auth/plugins";

export const auth = betterAuth({
    plugins: [
        captcha({
            provider: "vercel-botid",
            checkBotId,
        }),
    ],
});
```

When using `initBotId`, protect Better Auth's default auth routes at their actual request paths:

```ts
import { initBotId } from "botid/client/core";

initBotId({
    protect: [
        { path: "/api/auth/sign-up/email", method: "POST" },
        { path: "/api/auth/sign-in/email", method: "POST" },
        { path: "/api/auth/request-password-reset", method: "POST" },
    ],
});
```

BotID uses the browser's full request path. The plugin's `endpoints` option omits the Better Auth base path, as in `/sign-in/email`. Keep both in sync if you change `basePath` or `endpoints`.

With BotID initialized in the browser, sign in normally without a captcha token header:

```ts
import { authClient } from "@/lib/auth-client";

const { data, error } = await authClient.signIn.email({
    email: "user@example.com",
    password: "secure-password",
});
```

BotID does not support [native HTML form submissions](https://vercel.com/docs/botid/form-submissions).

Local development [allows requests by default](https://vercel.com/docs/botid/local-development-behavior), so verify the full flow on Vercel before relying on it in production. For route-specific detection levels, follow [Vercel's advanced configuration](https://vercel.com/docs/botid/advanced-configuration).

## How it works [#how-it-works]

<Steps>
  <Step>
    The plugin acts as a middleware: it intercepts requests to configured endpoints (see `endpoints`
    in the [Plugin Options](#plugin-options) section).
  </Step>

  <Step>
    it validates the captcha token on the server, by calling the captcha provider's `/siteverify`.

    <Callout type="info">
      **Vercel BotID** is the exception: it uses the supplied `checkBotId` function instead of a captcha token or `/siteverify` call.
    </Callout>
  </Step>

  <Step>
    * if the token is missing, gets rejected by the captcha provider, or if the `/siteverify` endpoint is
      unavailable, the plugin returns an error and interrupts the request.
    * if the token is accepted by the captcha provider, the middleware returns `undefined`, meaning the request is allowed to proceed.
    * for **Vercel BotID**, the request is rejected when `checkBotId` (or your custom `validateRequest`) determines it's a bot, and allowed to proceed otherwise.
  </Step>
</Steps>

## Plugin Options [#plugin-options]

* **`provider` (required)**: your captcha provider.
* **`secretKey` (required for all providers except `vercel-botid`)**: your provider's secret key used for the server-side validation.
* `endpoints` (optional): Choose which paths require CAPTCHA. By default, CAPTCHA protects `/sign-up/email`, `/sign-in/email`, and `/request-password-reset`. Requests to other paths don't require CAPTCHA. When you set `endpoints`, include every path you want to protect because it replaces the defaults. You can use exact paths or wildcard patterns like `/sign-in/*` for one segment and `/sign-in/**` for nested routes.
* `minScore` (optional - only *Google ReCAPTCHA v3*): minimum score threshold. Default is `0.5`.
* `siteKey` (optional - only *hCaptcha* and *CaptchaFox*): prevents tokens issued on one sitekey from being redeemed elsewhere.
* `siteVerifyURLOverride` (optional - token-based providers only): overrides the captcha verification URL.
* `checkBotId` (required for *Vercel BotID*): Vercel's server-side check function, imported from `botid/server` in your application. Wrap it in a function if you need to pass SDK options.
* `validateRequest` (optional - only *Vercel BotID*): custom validation logic. Return `true` to allow the request, `false` to reject it. By default, only results with `isBot: false` are allowed.

