# Turnstile CAPTCHA

Source: https://kart.bnomei.com/docs/forms/turnstile-captcha
Updated: 2025-08-08T14:05:34+00:00
Summary: Integrate Cloudflare Turnstile CAPTCHA in Kirby CMS: include JS snippet, configure site & secret keys, render widget, secure forms & validate tokens server.

## Javascript

Head over to the [official Turnstile documentation](https://developers.cloudflare.com/turnstile/) to read more about how to integrate Cloudflare's CAPTCHA solution.

Insert the Turnstile script snippet in your HTML’s `<head>` element:

Code (php):  
```
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
```

  
## Configuration

Within your Cloudflare account under **Turnstile** create a new Site and get the **site key** and **site secret**. Set both in your `.env` file.

Path: .env  
Code (bash):  
```
TURNSTILE_SITE_KEY=sitekey
TURNSTILE_SECRET_KEY=sitesecret
```

  
> Alternatively you could use the `bnomei.kart.turnstile.sitekey` and `bnomei.kart.turnstile.secretkey` config options. But using the `.env` file is safer.

## Widget

Render the Turnstile widget with the code from the documentation or use the following snippet `kart/turnstile-widget`.

Code (php):  
```
<?php snippet('kart/turnstile-widget') ?>
```

  
## Securing Forms

It might suffice to only secure routes accessible before the user is logged in, like the *Login*- and *Register/Sign-Up*-Forms. That is assuming bots do not create valid logins.

Use the code from the documentation or the following snippet `kart/turnstile-form`.

Code (php):  
```
<form action="<?= kart()->urls()->magiclink() ?>" method="POST">
    <label>
        <input type="email" name="email" required
                  placeholder="<?= t('email') ?>" autocomplete="email"
                  value="<?= urldecode(get('email', '')) ?>">
    </label>
    <?php snippet('kart/turnstile-form') ?>
    <input type="hidden" name="redirect" value="<?= url('kart/login') ?>?status=sent">
    <input type="hidden" name="success_url" value="<?= url('kart') ?>?msg=Welcome%20back">
    <button type="submit"><?= t('login') ?> <?= t('link') ?></button>
</form>
```

  
## Behind the Scenes

The endpoints intended for public use, which are most likely targeted by bots (login, register/signup, magic-link), are preconfigured with a check for the Turnstile token.

If posted with the form the `\Bnomei\Kart\Router::denied()`-helper will query the `https://challenges.cloudflare.com/turnstile/v0/siteverify` endpoint with your `secretkey` and check if the form request is legit or not.

On success, the form will continue as intended. If not, it will redirect to the error page or yield a `401` HTTP status code.