tallcms/filament-registration
A Filament v5 plugin that wires up Filament-native user registration with pluggable captcha (Cloudflare Turnstile, Google reCAPTCHA v3) and configurable default-role assignment.
Filament has built-in ->registration() support, but the default flow is just name/email/password — no captcha, no role assignment, no event bridge for hosts that listen on Laravel's Registered event. This plugin fills those gaps as a drop-in replacement that still uses Filament's documented hooks (no register() reimplementation, no internal copy-paste).
\Filament\Auth\Pages\Register. Login ↔ register cross-linking works for free.Illuminate\Auth\Events\Registered is fired alongside Filament's own Filament\Auth\Events\Registered event, so listeners written for Laravel's event keep working.composer require tallcms/filament-registration
php artisan migrate
Add to your panel provider:
use Tallcms\FilamentRegistration\Filament\FilamentRegistrationPlugin;
use Tallcms\FilamentRegistration\Filament\Pages\Register;
public function panel(Panel $panel): Panel
{
return $panel
->id('admin')
->path('admin')
->login()
->registration(Register::class) // explicit page class — required
->plugin(
FilamentRegistrationPlugin::make()
->defaultRole('member') // optional; null = no role assignment
);
}
Both ->registration(Register::class) and ->plugin(FilamentRegistrationPlugin::make()) are required and not interchangeable. ->registration() with no argument uses Filament's default page; the captcha and role assignment only run when the explicit class is wired.
Optional: enable Filament's built-in email verification with ->emailVerification(). The plugin defers to it — when the panel requires verification, Filament sends the verification email; when it doesn't, new users are pre-marked verified.
If your User model implements MustVerifyEmail, the plugin also makes sure the verification email always resolves a working link. It registers Illuminate\Auth\Notifications\VerifyEmail::createUrlUsing() pointing at Filament's own getVerifyEmailUrl(), so Laravel's default verification-listener flow (triggered by the Registered event bridge below) doesn't need a verification.verify route that Filament-only apps never define — and it skips Filament's own send when that listener already handled it, so you get exactly one email. This callback is only registered if your app hasn't already set its own (e.g. because it also runs a separate, non-Filament auth flow with its own verification route).
Captcha settings come from two layers, with the higher one winning:
The captcha provider is detected from whichever layer wins; you don't need to wire each provider individually.
All optional. The plugin works fully through the Admin UI without any of these set.
| Variable | Default | Purpose |
|---|---|---|
FILAMENT_REGISTRATION_CAPTCHA_ENABLED |
unset (auto) | Force captcha on (true) or off (false). When unset, captcha auto-enables if both SITE_KEY and SECRET_KEY are present, and stays off otherwise. |
FILAMENT_REGISTRATION_CAPTCHA_PROVIDER |
turnstile |
turnstile (Cloudflare) or recaptcha_v3 (Google). |
FILAMENT_REGISTRATION_CAPTCHA_SITE_KEY |
'' |
Public key embedded in the form. Safe to commit to source control if you want. |
FILAMENT_REGISTRATION_CAPTCHA_SECRET_KEY |
'' |
Private key for server-side verification. Keep out of source control — env or DB only. |
FILAMENT_REGISTRATION_CAPTCHA_RECAPTCHA_MIN_SCORE |
0.5 |
reCAPTCHA v3 only. Tokens scoring below this threshold are rejected. Range 0.0 (lenient) – 1.0 (strict). |
Example .env for production with Cloudflare Turnstile:
FILAMENT_REGISTRATION_CAPTCHA_ENABLED=true
FILAMENT_REGISTRATION_CAPTCHA_PROVIDER=turnstile
FILAMENT_REGISTRATION_CAPTCHA_SITE_KEY=0x4AAAAAAA...
FILAMENT_REGISTRATION_CAPTCHA_SECRET_KEY=0x4AAAAAAA...
The Admin UI's "Secret key status" indicator reads ✓ Configured when either layer (DB or env) supplies a secret. So if your secret lives in .env, you can leave the form's secret field blank — the form treats blank as "keep current".
By default the page is accessible to any user who can reach the panel (i.e. anyone who passes the panel's auth middleware). The plugin doesn't ship a built-in permission gate because Filament users have very different auth setups (some use Shield, some Bouncer, some plain canAccess(), some panel-level middleware). Pick whichever fits your app:
If you're running TallCMS with the Multisite plugin (the SaaS-style site-builder mode), install the tallcms/registration bridge plugin and you're done — the bridge gives you the full SaaS signup flow out of the box:
site_owner (with TallCMS's role-aware policy scoping so each new user only sees their own sites)/register URL to the Filament-native page# In a TallCMS + Multisite install
composer require tallcms/filament-registration
php artisan migrate
# Then upload the tallcms/registration bridge plugin via Admin → Plugins
No subclassing, no canAccess() override — TallCMS Multisite handles it. Read more at tallcms.com.
The bridge plugin is mostly Multisite-coupled (its onboarding redirect and site-plan assignment both no-op without the Multisite plugin). For a vanilla TallCMS install, skip the bridge and use this plugin directly:
$panel
->registration(\Tallcms\FilamentRegistration\Filament\Pages\Register::class)
->plugin(
FilamentRegistrationPlugin::make()
->defaultRole('author') // or whichever role you want
);
On gating the settings page: TallCMS's panel already requires authenticated admin access (canAccessPanel()), so unless you want a stricter gate (e.g. "only super_admin can edit captcha keys, not editor or author"), you don't need to add anything else. If you do want that stricter cut, see the Shield subclass recipe below — note that Shield can't auto-discover this page (it's a vendor class without HasPageShield), so the subclass is the canonical wire-up; no shield:generate magic alternative exists.
Subclass the page and add the trait, then point your panel at the subclass:
namespace App\Filament\Pages;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Tallcms\FilamentRegistration\Filament\Pages\RegistrationSettings as BaseRegistrationSettings;
class RegistrationSettings extends BaseRegistrationSettings
{
use HasPageShield;
}
Then run php artisan shield:generate --page=RegistrationSettings --panel=<id> once and grant the resulting View:RegistrationSettings permission to roles via the Shield UI.
canAccess() usersSubclass and override:
public static function canAccess(): bool
{
return auth()->user()?->is_admin ?? false;
}
Optional dependency note: role assignment via defaultRole(...) requires spatie/laravel-permission (suggested in composer.json). Hosts without Spatie permissions get role assignment as a no-op — no fatal error, the user is still created.
Bind a custom response in your service provider:
use Filament\Auth\Http\Responses\Contracts\RegistrationResponse;
use App\Http\Responses\OnboardingRegistrationResponse;
public function register(): void
{
$this->app->bind(RegistrationResponse::class, OnboardingRegistrationResponse::class);
}
Your concrete just needs to implement RegistrationResponse (which extends Responsable) and return a RedirectResponse from toResponse().
->emailVerification() panel method. No custom verification view is shipped.MIT.
How can I help you explore Laravel packages today?