Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Turnstile Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Laragear/Turnstile is the batteries-included Laravel client for Cloudflare's Turnstile CAPTCHA replacement. Day one looks like this:

  1. Install and publish config:
    composer require laragear/turnstile
    php artisan vendor:publish --provider="Laragear\Turnstile\TurnstileServiceProvider"
    
  2. Set 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.
  3. Your first use case is the classic contact/registration form: drop the Blade component into the form and add the validation rule to the controller or FormRequest.

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.

Implementation Patterns

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.

Gotchas and Tips

  • Tokens are single-use and short-lived (~5 minutes). Re-validating the same token, or validating in middleware and a rule, silently double-spends it. One check per token.
  • Token field name matters. The hidden input is 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.
  • Action/cdata binding. If you set 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.
  • Filament on Livewire 4. Pre-2.1.1 the Filament widget breaks under Livewire 4's updated component lifecycle. The symptom is a widget that renders but never submits a token (or throws on re-render). The fix is a version bump, not config — check composer show laragear/turnstile first when a Filament login page misbehaves after a Livewire upgrade.
  • Behind a proxy/load balancer. Siteverify accepts a remote IP; if you pass one, make sure it's the real client IP from TrustProxies, not the load balancer, or legitimate users get scored as suspicious.
  • Config cache. Changing keys in .env on a cached-config server does nothing until php artisan config:cache is re-run. This burns people during key rotation.
  • Never validate on GET. The token only exists after the widget renders and the user submits; adding the rule to a page that just displays the form will fail every time.
  • Multiple widgets on one page need distinct container IDs and explicit rendering, otherwise the second x-turnstile can end up empty.
  • Testing without 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.
  • Extension point: the service resolves through the container, so you can bind a subclass or swap the HTTP client options in config/turnstile.php for timeouts and retries rather than touching vendor code.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo