eii / filament-turnstile
Cloudflare Turnstile integration for Filament 5 forms and auth scaffolding with action-aware validation.
Requires
- php: ^8.2
- filament/forms: ^5.0
- illuminate/http: ^11.28|^12.0|^13.0
- illuminate/support: ^11.28|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Cloudflare Turnstile integration for Filament 5 forms and auth scaffolding, with action-aware validation and analytics-friendly tracking.
This package gives you:
- a reusable
TurnstileFilament form component, - strict server-side Cloudflare Siteverify validation,
- scaffolding commands for custom Filament auth pages (Login, Register, Request Password Reset),
- per-form/per-page action support so you can reuse the same site/secret keys while still tracking each flow separately.
Requirements
- PHP 8.2+
- Laravel 11.28+ (also supports 12 and 13 via Illuminate constraints)
- Filament Forms 5.x
Installation
Local path install (development)
In your Laravel app composer.json:
{
"repositories": [
{
"type": "path",
"url": "../eii-filament-turnstile",
"options": {
"symlink": true
}
}
]
}
Then require it:
composer require eii/filament-turnstile:@dev
Packagist install
composer require eii/filament-turnstile
Laravel will auto-discover the service provider.
Configuration
Publish config:
php artisan vendor:publish --tag=filament-turnstile-config
Add required env values:
TURNSTILE_SITE_KEY=your-site-key TURNSTILE_SECRET_KEY=your-secret-key TURNSTILE_ALLOWED_HOSTNAMES=yourdomain.com TURNSTILE_ALLOW_MISSING_ACTION=false
Important behavior
TURNSTILE_ALLOWED_HOSTNAMESis mandatory and fail-closed (empty list rejects all).- Hostnames must be comma-separated plain hosts (no scheme/path/port).
- Keep
TURNSTILE_ALLOW_MISSING_ACTION=falsein production. - Action names, widget options, and HTTP timeouts are configured in
config/filament-turnstile.php.
Example actions block:
'actions' => [ 'login' => 'admin-login', 'register' => 'admin-register', 'request_password_reset' => 'admin-password-reset-request', ],
Why actions matter (tracking + security)
You can use the same Turnstile site key and secret key across multiple forms/pages, while assigning a unique action per flow.
Example actions:
admin-loginadmin-registeradmin-password-reset-requestadmin-users-createadmin-orders-update
Benefits:
- Cloudflare analytics can distinguish traffic by flow.
- Server-side validation checks the expected action to prevent token reuse across unrelated forms.
Use in a Filament form
use Eii\FilamentTurnstile\Forms\Components\Turnstile; use Filament\Schemas\Schema; public function form(Schema $schema): Schema { return $schema->components([ // ... your fields Turnstile::make('turnstile_token') ->label('Security Verification') ->turnstileAction('admin-users-create') ->theme('auto') ->size('normal') ->appearance('interaction-only'), ]); }
Notes:
- Field is required.
- Token is verified server-side via Cloudflare Siteverify.
- Don’t trust frontend callback alone.
After a failed submit, you can reset the widget from Livewire:
$this->dispatch('eii-turnstile-reset');
Auth page scaffolding commands (Filament 5)
Generate custom Turnstile-enabled auth pages:
php artisan make:filament-turnstile-login php artisan make:filament-turnstile-register php artisan make:filament-turnstile-request-password-reset
Each command supports --force to overwrite existing files.
Generated classes:
App\Filament\Pages\Auth\TurnstileLoginApp\Filament\Pages\Auth\TurnstileRegisterApp\Filament\Pages\Auth\TurnstileRequestPasswordReset
Register them in your Panel provider auth setup. Example:
->login(\App\Filament\Pages\Auth\TurnstileLogin::class)
(Registration/password-reset wiring may differ depending on your panel config style.)
Direct verifier usage
use Eii\FilamentTurnstile\Contracts\TurnstileVerifier; $result = app(TurnstileVerifier::class)->verify( token: $token, remoteIp: request()->ip(), expectedAction: 'admin-login', ); if (! $result->success) { // reject request }
Local testing with Cloudflare test keys
Cloudflare provides official test keys that work on localhost and CI. Reference: https://developers.cloudflare.com/turnstile/troubleshooting/testing/
| Site key | Secret key | Behavior |
|---|---|---|
1x00000000000000000000AA |
1x0000000000000000000000000000000AA |
Always passes |
2x00000000000000000000AB |
2x0000000000000000000000000000000AA |
Always fails |
1x00000000000000000000AA |
3x0000000000000000000000000000000AA |
Fails (token already spent) |
3x00000000000000000000FF |
1x0000000000000000000000000000000AA |
Interactive challenge then pass |
Example local env:
TURNSTILE_SITE_KEY=1x00000000000000000000AA TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA TURNSTILE_ALLOWED_HOSTNAMES=example.com TURNSTILE_ALLOW_MISSING_ACTION=true
With test keys, Cloudflare commonly returns hostname=example.com and action=test, so configure accordingly for local tests.
Security model
- Always validates with Cloudflare server-side.
- Rejects invalid/missing/oversized tokens.
- Enforces hostname allowlist.
- Enforces action match (unless missing action is explicitly allowed).
- Fails closed on API/network/config problems.
- Does not log secrets or tokens.
Current scope includes reusable field + verification service + auth-page scaffolding for Login, Register, and Request Password Reset.