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

Filament Turnstile Laravel Package

happenv-com/filament-turnstile

Filament Turnstile adds a Cloudflare Turnstile CAPTCHA field to your Filament forms, letting you protect admin panels and public forms from bots and spam without puzzles or user friction. Simple to install, configure, and drop into any form schema.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up in five minutes

composer require happenv-com/filament-turnstile
php artisan vendor:publish --tag="filament-turnstile-config"

Create a Turnstile widget in the Cloudflare dashboard (Account → Turnstile → Add site) and copy the site key (public) and secret key (server-side only). Put them in .env:

TURNSTILE_SITE_KEY=0x4AAAAAAA...
TURNSTILE_SECRET_KEY=0x4AAAAAAA...

Then clear cached config — this is the single most common "it silently does nothing" cause:

php artisan config:clear

Where to look first

  1. config/filament-turnstile.php — the published config is the source of truth for key names, the siteverify endpoint, and any bypass/testing flag. Read it before reading the source.
  2. src/Forms/Components/Turnstile.php — the Filament form component; its public setters tell you exactly which options (theme, size, language, mode) are supported in this version.
  3. The package README usage block for the exact namespace of the component class — it has moved between major versions of similar packages, so copy it rather than guessing.

First use case

Add the field to any Filament form and let the package's validation rule do the siteverify call:

use Happenv\FilamentTurnstile\Forms\Components\Turnstile;

public static function form(Form $form): Form
{
    return $form->schema([
        TextInput::make('email')->email()->required(),
        Textarea::make('message')->required(),
        Turnstile::make('captcha')->required(),
    ]);
}

The validation rule posts the token to https://challenges.cloudflare.com/turnstile/v0/siteverify with your secret and fails the field if Cloudflare returns success: false. You get server-side enforcement for free; never rely on the widget merely being present.

Implementation Patterns

Protecting the login and registration pages

Filament's auth pages aren't resource forms, so you can't just add the component to a form() method. The practical routes are:

  • Extend the panel's login page class and override the form schema, then register it with ->login(YourLogin::class) on the panel.
  • For password reset / registration, the same pattern applies via ->registration(), ->passwordReset().
  • Keep the Turnstile field last in the schema so users fill the form before the token is minted — tokens are short-lived and single-use.

Scoping to specific forms only

Don't add captcha globally. Wrap it so it only appears where abuse is realistic:

Turnstile::make('captcha')
    ->visible(fn () => ! auth()->check())   // anonymous submissions only
    ->required()

This keeps friction off authenticated admin workflows, which is where most day-to-day complaints about captchas originate.

Testing and local development

Cloudflare publishes always-pass test keys. Use them in phpunit.xml rather than mocking HTTP:

<env name="TURNSTILE_SITE_KEY" value="1x00000000000000000000AA"/>
<env name="TURNSTILE_SECRET_KEY" value="1x0000000000000000000000000000000AA"/>

If the package exposes a bypass/fake helper in the config (check config/filament-turnstile.php for a enabled or bypass style key in 1.2.x), prefer toggling that per-environment over conditionally removing the component — removing it changes the form schema between test and production, which hides bugs.

For Livewire feature tests that submit a Filament form, use Livewire::test(...)->fillForm([...])->call('create') and assert the record exists; with test keys the siteverify call still happens, so either keep the HTTP call live (slow, needs network) or bind a fake HTTP client with Http::fake() returning ['success' => true].

Multiple widgets on one page

The Turnstile script is loaded once and renders widgets by container. If you render two forms on a single page (e.g. a modal on top of a page form), verify both widgets render — most integration bugs here come from the script only auto-rendering on first paint. If needed, use explicit rendering with a render callback and a distinct container id per form.

Spam-response workflow

Log the siteverify payload (at minimum error-codes and hostname) whenever validation fails. Cloudflare's failure reasons are genuinely actionable: invalid-input-response (expired/reused token) means a UX problem on your side, invalid-input-secret means a deployment/config problem, hostname-mismatch means you reused keys across environments.

Gotchas and Tips

  • Tokens expire after 300 seconds and are single-use. A form the user leaves open, or a failed validation that re-renders the form, will submit a dead token. The widget must be reset after any server-side validation error, or users get stuck in a loop where the captcha looks solved but never validates. Test the "submit with a missing required field, then resubmit" path explicitly — this is the number one field bug with Turnstile integrations.
  • Livewire will nuke the widget. DOM diffing on re-render can detach the Turnstile iframe. If you see a blank box after any wire:model update, wrap the field container in wire:ignore (via the component's extraAttributes) or check whether the package already does this in its Blade view.
  • config:cache in production. Keys read via env() inside a cached config are frozen. Set the env vars, then re-run php artisan config:cache as part of deploy — a missing secret key otherwise fails open-looking but closed (every submission rejected) or, worse, if the package treats an empty secret as "disabled", fails open.
  • CSP and ad-blockers. If you run a Content-Security-Policy, allow https://challenges.cloudflare.com for script-src and frame-src. Brave's shields and some privacy extensions block Turnstile outright; make sure the form fails with a readable message rather than a silent required error on an invisible field.
  • Hostname binding. Keys are bound to the hostnames configured in the Cloudflare dashboard. Staging on staging.example.com with production keys will fail with hostname-mismatch. Register staging hostnames or use test keys outside production.
  • Don't skip server-side validation. The component's presence is cosmetic; the security guarantee is entirely the validation rule's siteverify request. If you build a custom API endpoint or a non-Filament form (e.g. a plain Laravel controller), reuse the package's rule rather than reimplementing the HTTP call.
  • Upgrading from 1.0.0 to 1.2.0. The published release notes for 1.2.0 are only a compare link (v1.0.0...v1.2.0) with no prose. Before upgrading, open that diff and skim src/ and config/ for renamed config keys or changed component signatures — you're skipping a minor version boundary, so treat it as a real review rather than a patch bump. Pin ^1.2 and run your form tests after the bump.
  • Extension points worth knowing. The component's ->theme(), ->size(), and ->language() setters map directly to Turnstile's render options — useful for matching a dark-mode admin panel. If you need to customise markup, the Blade view is publishable/overridable; keep overrides minimal so upgrades stay cheap.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager