Enter a search term to find articles.
How to Add Cloudflare Turnstile to Laravel in 5 Minutes (No Packages Required)

How to Add Cloudflare Turnstile to Laravel in 5 Minutes (No Packages Required)

November 16, 2025
63 views

If you’ve built a public-facing web app, you eventually run into the same annoying problem: bots. They fill out your forms, spam your inbox, and waste your server’s time.

Most developers immediately think of Google reCAPTCHA, but there’s a cleaner option that solves the same problem without the tracking baggage: Cloudflare Turnstile.

Turnstile is Cloudflare’s privacy-friendly CAPTCHA alternative.

  • ❌ No user friction.
  • 🚍 No weird “click the traffic lights” puzzles. (ehem.. Google)
  • ⏲️ And setup takes only a few minutes.

In this quick guide, I’ll show you why you should use Turnstile — and how to integrate it into a Laravel app without any third-party packages.

Why use Cloudflare Turnstile?

Here are the biggest reasons I migrated to it:

1. No user frustration

Turnstile doesn’t make users solve puzzles. It verifies silently in the background unless something looks suspicious.

2. Privacy-respecting

Unlike reCAPTCHA, Turnstile doesn’t track users across the web. No profiling, no annoying consent banners.

3. Dead simple to implement

It’s literally:

  • Load a script
  • Place a widget
  • Verify the token server-side

Done!

4. Completely free

Cloudflare offers Turnstile with unlimited usage on the free plan.

Step 1 — Create a Turnstile Widget in Cloudflare

  1. Log in to Cloudflare
  2. Application Security → Turnstile
  3. Click Add Widget
  4. Set:
    • Name: e.g: my-domain-name-turnstile
    • Add Hostnames: If you already have existing hostnames setup in Cloudflare you can select it, or you can just type your domain name manually. You can also add your local domains here - very useful if you are using Herd on your local machine.
    • Widget Mode: Managed (recommended)
    • Would you like to opt for pre-clearance for this site?: Just select "No"
  5. Copy your Site Key and Secret Key

Step 2 — Add the Keys, Widget and Script into Laravel

Add these to your .env file

# Cloudflare Turnstile Captcha
TURNSTILE_ENABLED=true # This will be so that you can just disable it quickly, especially on your tests
TURNSTILE_URL=https://challenges.cloudflare.com/turnstile/v0/siteverify
TURNSTILE_SITE_KEY=Your-Turnstile-Site-Key
TURNSTILE_SECRET_KEY=Your-Turnstile-Secret-Key

Then, configure these values on your config/services.php file

// On services.php
'turnstile' => [
    'enabled' => env('TURNSTILE_ENABLED', false),
    'turnstile_url' => env('TURNSTILE_URL'),
    'site_key' => env('TURNSTILE_SITE_KEY'),
    'secret_key' => env('TURNSTILE_SECRET_KEY'),
],

Then add the Widget into your Blade template — this usually goes into public facing forms such as registration, forgot password, contact us, etc.

<div class="cf-turnstile" data-sitekey="{{ config('services.turnstile.site_key') }}">
</div>

Then add their script outside the body of your base template

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

Step 3 — Create a Rule class and use it on your form validations

This sample rule class below provides a way to ignore required validation on the Turnstile field if the config('services.turnstile.enabled') is set to false.

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Http;

class Turnstile implements Rule
{
    /**
     * Get the validation rules for the Turnstile field.
     * Returns an array with 'required' if Turnstile is enabled, otherwise just the rule.
     *
     * @return array
     */
    public static function rules(): array
    {
        $rules = [new self()];

        if (config('services.turnstile.enabled', false)) {
            array_unshift($rules, 'required');
        }

        return $rules;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        // If Turnstile is not enabled, skip validation
        if (!config('services.turnstile.enabled', false)) {
            return true;
        }

        // If Turnstile is enabled but value is empty, validation fails
        if (empty($value)) {
            return false;
        }

        try {
            $response = Http::asForm()->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
                'secret' => config('services.turnstile.secret_key'),
                'response' => $value,
                'remoteip' => request()->ip(),
            ]);

            $result = $response->json();

            return isset($result['success']) && $result['success'] === true;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The Turnstile verification failed. Please try again.';
    }
}

The add the validation field on your controller

// On your Controller...
public function __invoke(Request $request)
{
    $request->validate([
        'cf-turnstile-response' => Turnstile::rules(),
    ]);
}

Turnstile sends the token as cf-turnstile-response, so the validation rule catches empty/missing tokens before the request even reaches Cloudflare.

Step 4 — Done. Your form is now protected.

Once this is in place, bots will struggle to submit your forms — and your legitimate users won’t even notice the protection.

You also avoid the privacy overhead and UI friction of Google’s CAPTCHA.

Final Thoughts

After setting up Turnstile on my Laravel apps, the difference was immediate:

  • Zero spam submissions
  • No broken experiences on mobile
  • No "pick all the buses" nightmares
  • Everything runs smoothly behind the scenes

If you’re looking for a modern, developer-friendly CAPTCHA alternative, Turnstile is an easy win!

Marvin Quezon

Marvin Quezon

Full Stack Web Developer

Marvin Quezon · Copyright © 2025 · Privacy · Sitemap