laragear/turnstile
Laragear Turnstile integrates Cloudflare's frictionless, privacy-friendly CAPTCHA into Laravel. It ships validation rules, middleware, Blade components, and helpers for seamless server-side verification, letting you protect forms from bots and spam without puzzles or user friction.
Laragear/Turnstile is the batteries-included Laravel client for Cloudflare's Turnstile CAPTCHA replacement. Day one looks like this:
composer require laragear/turnstile
php artisan vendor:publish --provider="Laragear\Turnstile\TurnstileServiceProvider"
TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY in .env. In local/testing environments use Cloudflare's dummy keys (1x00000000000000000000AA / 1x0000000000000000000000000000000AA) so you never hit the real siteverify endpoint.Where to look first:
README.md / docs in the repo — the happy path is documented end to end and is short.config/turnstile.php after publishing — it wires the site key, secret key, HTTP client options, and the middleware/route behaviour.src/ — the package is small enough to read in one sitting (rule, Blade component, middleware, service that calls /turnstile/v0/siteverify, plus the optional Filament widget).composer.json — check the version constraint you pulled; 2.1.1 is the release that fixes the Filament widget for Livewire 4, so pin ^2.1.1 if you're on Livewire 4.Blade form (the 90% case). Render the widget inside the form and validate the injected cf-turnstile-response field. Keep the script include in your layout once rather than per-form if you render several widgets.
<form method="POST" action="/contact">
@csrf
<x-turnstile />
<button type="submit">Send</button>
</form>
$request->validate([
'cf-turnstile-response' => ['required', 'turnstile'],
]);
FormRequests. This is the cleanest place for it — put the rule in rules() and every consumer of the request is protected without repeating the token field name.
Middleware pattern. For routes where you want the gate to run before the controller at all, apply the package middleware (aliased in the service provider) to the POST route:
Route::post('/contact', ContactController::class)->middleware('turnstile');
Use either middleware or a validation rule per request — not both. The token is single-use, so the second check will always fail.
Facade / programmatic checks. For non-form entry points (SPA JSON endpoints, Livewire actions, queued jobs that received a token), resolve the service or facade and call the validate method directly, branching on the boolean response rather than throwing. This is where you also pass the client IP if you want Cloudflare to score against it.
Livewire and Filament. The component works in Livewire forms, but the token has to be part of the same request that hits the server — bind the widget to the Livewire submit, not to a plain <form>. If you're on Filament, the bundled widget is the supported path; v2.1.1 is the release that makes it work under Livewire 4, so treat that as a minimum version on any Filament 4 / Livewire 4 panel and upgrade before debugging anything else.
Testing pattern. Fake the outbound HTTP call instead of disabling the check:
Http::fake([
'challenges.cloudflare.com/*' => Http::response(['success' => true]),
]);
This keeps the rule and middleware in the request lifecycle (so you still catch a missing token field), while removing the network dependency from CI.
Config-driven environments. Put the keys in config/turnstile.php sourced from .env, and let the dummy keys come from .env.testing. That way php artisan config:cache in production and local testing share one code path.
cf-turnstile-response. If you rename inputs or use a custom JS callback, you must tell the validator where to look — the default rule expects the standard name.data-action or data-cdata on the widget, the siteverify response echoes it back; validating a token issued for a different action is a common cause of "it works locally, fails on the second form". Keep actions unique per form.composer show laragear/turnstile first when a Filament login page misbehaves after a Livewire upgrade.TrustProxies, not the load balancer, or legitimate users get scored as suspicious..env on a cached-config server does nothing until php artisan config:cache is re-run. This burns people during key rotation.x-turnstile can end up empty.Http::fake will make real network calls to Cloudflare and give flaky, rate-limited results in CI — the fake or the dummy keys are not optional.config/turnstile.php for timeouts and retries rather than touching vendor code.How can I help you explore Laravel packages today?