coderflex/laravel-turnstile
Add Cloudflare Turnstile CAPTCHA to Laravel with minimal setup. Includes config publishing, env-based site/secret keys, validation integration, and customizable/translatable error messages for protecting forms and endpoints from bots.
Installation:
composer require coderflex/laravel-turnstile
php artisan vendor:publish --tag="turnstile-config"
Add your Cloudflare Turnstile keys to .env:
TURNSTILE_SITE_KEY=your_site_key
TURNSTILE_SECRET_KEY=your_secret_key
First Use Case: Add the Turnstile widget to a Blade form:
<x-turnstile-widget theme="dark" language="en-US" />
Validate the response in your controller:
use Coderflex\LaravelTurnstile\Facades\LaravelTurnstile;
public function store(Request $request) {
$response = LaravelTurnstile::validate();
if (!$response['success']) {
return back()->withErrors(['captcha' => 'Invalid CAPTCHA']);
}
}
<x-turnstile-widget /> component in forms where bot protection is needed (registration, contact forms, etc.).<x-turnstile-widget
theme="auto"
size="compact"
language="es"
callback="handleTurnstileSubmit"
/>
@error('cf-turnstile-response')
<p class="text-red-500">{{ $message }}</p>
@enderror
LaravelTurnstile::validate() to check the response in controllers:
$result = LaravelTurnstile::validate($request->input('cf-turnstile-response'));
if (!$result['success']) {
return back()->withInput()->withErrors(['captcha' => trans('turnstile.error_messages.turnstile_check_message')]);
}
use Coderflex\LaravelTurnstile\Rules\TurnstileCheck;
$request->validate([
'cf-turnstile-response' => ['required', new TurnstileCheck()],
]);
$response = LaravelTurnstile::validate($request->input('turnstile_token'));
if (!$response['success']) {
return response()->json(['error' => 'Invalid CAPTCHA'], 400);
}
.env for local development:
TURNSTILE_SITE_KEY=0x4AAAAAAAAAAAAAAAAAAAAAGGeSlP9
TURNSTILE_SECRET_KEY=0x4AAAAAAAAAAAAAAAAAAAAAGGeSlQ
$this->mock(LaravelTurnstile::class)->shouldReceive('validate')
->once()
->andReturn(['success' => true]);
Missing .env Keys:
TURNSTILE_SITE_KEY or TURNSTILE_SECRET_KEY are missing, the package throws a RuntimeException. Always validate these in .env before use.if (config('turnstile.turnstile_secret_key') === null) {
throw new \RuntimeException('Turnstile secret key not configured.');
}
Incorrect Field Name:
cf-turnstile-response by default. If you rename this in your form, the validation will fail.LaravelTurnstile::validate($request->input('custom_turnstile_field'));
Rate Limiting:
429 errors.JavaScript Dependency:
https://challenges.cloudflare.com/turnstile/v0/api.js). Ensure it’s loaded before the widget renders.@include('turnstile::script')
Enable Logging:
Add debug logs to config/turnstile.php to trace validation issues:
'debug' => env('TURNSTILE_DEBUG', false),
Then check Laravel logs for Turnstile-related entries.
Raw API Response: Inspect the raw response from Cloudflare for debugging:
$response = LaravelTurnstile::validate();
\Log::debug('Turnstile raw response:', $response);
Custom Error Messages:
Override the default error message in config/turnstile.php:
'error_messages' => [
'turnstile_check_message' => 'You must complete the verification process.',
],
Extend Validation Logic:
Subclass the TurnstileCheck rule to add custom logic:
use Coderflex\LaravelTurnstile\Rules\TurnstileCheck as BaseTurnstileCheck;
class CustomTurnstileCheck extends BaseTurnstileCheck {
public function passes($attribute, $value) {
$result = parent::passes($attribute, $value);
// Add custom logic here
return $result;
}
}
Dynamic Widget Configuration: Use Blade directives to dynamically set widget attributes:
@turnstileWidget(['theme' => 'dark', 'language' => app()->getLocale()])
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" defer></script>
How can I help you explore Laravel packages today?