| Install | |
|---|---|
composer require spykapps/filament-passwordless-login |
|
| Latest Version: | v1.0.1 |
| PHP: | ^8.1 |

A highly customizable Filament 4 & 5 plugin for passwordless (magic link) authentication — built on top of spykapps/passwordless-login.
composer require spykapps/filament-passwordless-login
This will also install spykapps/passwordless-login as a dependency.
If you haven't already set up the base package, publish and run the migrations:
php artisan vendor:publish --tag=passwordless-login-config
php artisan vendor:publish --tag=passwordless-login-migrations
php artisan migrate
Important: The
passwordless_login_tokenstable must exist before using this plugin. If you've already run the migration, skip this step.
use SpykApp\PasswordlessLogin\Traits\HasMagicLogin;
class User extends Authenticatable
{
use HasMagicLogin;
}
use SpykApp\FilamentPasswordlessLogin\FilamentPasswordlessLoginPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(
FilamentPasswordlessLoginPlugin::make()
);
}
That's it! The plugin will replace the login page, register the token resource with widgets.
php artisan vendor:publish --tag=filament-passwordless-login-config
php artisan vendor:publish --tag=filament-passwordless-login-lang
All options can be set fluently in the plugin registration:
use SpykApp\FilamentPasswordlessLogin\FilamentPasswordlessLoginPlugin;
use SpykApp\FilamentPasswordlessLogin\Enums\FilamentPasswordlessLoginActionPosition;
FilamentPasswordlessLoginPlugin::make()
// ── Login Page ──────────────────────────────────────
->loginPage() // Enable magic link login (default: true)
->loginPage(false) // Disable — keep default Filament password login
->login(MyCustomLoginPage::class) // Use your own custom login page class
->showPasswordLoginLink() // Show "Back to password login" link
->showPasswordLoginLink(false) // Hide it
// ── Redirect URLs ───────────────────────────────────
->redirectUrl('/admin/dashboard') // Where to go after login (default: auto-detects panel URL)
->failureUrl('/admin/login') // Where to go on expired/invalid link (default: auto-detects panel login URL)
// ── Login Action ────────────────────────────────────
->loginAction() // Enable login action (default: false)
->loginAction(false) // Disable
->loginActionPosition( // Where to show the action
FilamentPasswordlessLoginActionPosition::EmailFieldHint // As hint on email field
// or
FilamentPasswordlessLoginActionPosition::LoginFormEndButton // As button after login form
)
->loginActionIcon('heroicon-m-sparkles') // Custom icon
->loginActionColor('warning') // Custom color
// ── Action Modal ────────────────────────────────────
->slideover() // Open action modal as slide-over
->slideover(false) // Open as centered modal (default)
// ── Custom Mailable / Notification ──────────────────
->mailable(\App\Mail\MyMagicLinkMail::class) // Custom mailable class
->notification(\App\Notifications\MyMagicLinkNotif::class) // Custom notification class
// ── Resource ────────────────────────────────────────
->resource() // Enable token resource (default: true)
->resource(false) // Disable
->canCreateTokens() // Allow manual token creation (default: true)
->canCreateTokens(false) // Disable create
->canDeleteTokens() // Allow deletion (default: true)
->canDeleteTokens(false) // Disable delete
->resourceSlug('magic-links') // Custom URL slug
->navigationGroup('Security') // Custom nav group (or use lang file)
->navigationIcon('heroicon-o-key') // Custom nav icon
->navigationSort(50) // Custom sort order
// ── Widgets (shown on resource page) ────────────────
->statsWidget() // Enable stats widget (default: true)
->statsWidget(false) // Disable
->chartsWidget() // Enable chart widgets (default: true)
->chartsWidget(false) // Disable
->chartDays(60) // Custom chart time range in days
Settings follow this priority order: Plugin fluent API → Config file → Language file → Hardcoded default
| Setting | Plugin API | Config | Lang | Default |
|---|---|---|---|---|
| Icon | ->loginActionIcon() |
login_action.icon |
— | heroicon-m-link |
| Color | ->loginActionColor() |
login_action.color |
— | primary |
| Slideover | ->slideover() |
login_action.slideover |
— | false |
| Nav Group | ->navigationGroup() |
— | navigation_group |
Authentication |
| Nav Label | — | — | navigation_label |
Magic Links |
| Mailable | ->mailable() |
— | — | Base package default |
| Notification | ->notification() |
— | — | Base package default |
| Redirect URL | ->redirectUrl() |
passwordless-login.redirect.on_success |
— | filament()->getUrl() |
| Failure URL | ->failureUrl() |
passwordless-login.redirect.on_failure |
— | filament()->getLoginUrl() |
The plugin extends Filament's native Filament\Pages\Auth\Login — no custom views or Blade templates needed.
The form shows only an email field. On submit, a magic link is sent and the page switches to a "Check your email!" confirmation state.
FilamentPasswordlessLoginPlugin::make()
->loginPage()
FilamentPasswordlessLoginPlugin::make()
->loginPage()
->showPasswordLoginLink()
FilamentPasswordlessLoginPlugin::make()
->loginPage(false)
By default, the plugin auto-detects redirect URLs from the current Filament panel — no configuration needed in most cases.
| URL | Default (auto-detected) | Description |
|---|---|---|
| Redirect URL | filament()->getUrl() (e.g. /admin) |
Where the user goes after clicking the magic link |
| Failure URL | filament()->getLoginUrl() (e.g. /admin/login) |
Where the user goes when a link is expired, invalid, or used |
FilamentPasswordlessLoginPlugin::make()
->redirectUrl('/admin/dashboard')
->failureUrl('/admin/login?error=expired')
->redirectUrl() / ->failureUrl()passwordless-login.redirect.on_success / on_failureEach panel automatically uses its own URLs. No extra configuration needed:
// Admin panel → redirects to /admin after login
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->path('admin')
->plugin(FilamentPasswordlessLoginPlugin::make());
}
}
// App panel → redirects to /app after login
class AppPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->path('app')
->plugin(FilamentPasswordlessLoginPlugin::make());
}
}
// Admin panel
FilamentPasswordlessLoginPlugin::make()
->redirectUrl('/admin/dashboard')
->failureUrl('/admin/login')
// App panel
FilamentPasswordlessLoginPlugin::make()
->redirectUrl('/app/home')
->failureUrl('/app/login')
Extend the plugin's login page:
<?php
namespace App\Filament\Pages\Auth;
use SpykApp\FilamentPasswordlessLogin\Pages\MagicLinkLogin;
use Filament\Schemas\Schema;
use Filament\Forms\Components\TextInput;
use Illuminate\Contracts\Support\Htmlable;
class MyMagicLogin extends MagicLinkLogin
{
public function getHeading(): string|Htmlable
{
return 'Welcome! Sign in securely.';
}
public function getSubheading(): string|Htmlable|null
{
if ($this->magicLinkSent) {
return 'A secure link has been sent to your inbox.';
}
return 'No password needed — we\'ll email you a login link.';
}
}
Register it:
FilamentPasswordlessLoginPlugin::make()
->login(\App\Filament\Pages\Auth\MyMagicLogin::class)
The SendMagicLinkAction can be used in two positions on the login page, or standalone anywhere in your panel.
Adds a clickable hint icon next to the email field on the default Filament password login page:
FilamentPasswordlessLoginPlugin::make()
->loginPage(false) // Keep the default password login
->loginAction() // Enable the action
->loginActionPosition(FilamentPasswordlessLoginActionPosition::EmailFieldHint)
Renders a button below the sign-in button on the default Filament password login page:
FilamentPasswordlessLoginPlugin::make()
->loginPage(false) // Keep the default password login
->loginAction() // Enable the action
->loginActionPosition(FilamentPasswordlessLoginActionPosition::LoginFormEndButton)
FilamentPasswordlessLoginPlugin::make()
->loginAction()
->loginActionIcon('heroicon-m-envelope')
->loginActionColor('success')
->slideover() // Open modal as slide-over
Use SendMagicLinkAction anywhere in your Filament panel — page headers, table actions, form hint actions:
use SpykApp\FilamentPasswordlessLogin\Actions\SendMagicLinkAction;
protected function getHeaderActions(): array
{
return [
SendMagicLinkAction::make(),
];
}
use SpykApp\FilamentPasswordlessLogin\Actions\SendMagicLinkAction;
public static function table(Table $table): Table
{
return $table
->columns([...])
->actions([
SendMagicLinkAction::make()
->fillForm(fn ($record) => ['email' => $record->email]),
]);
}
use SpykApp\FilamentPasswordlessLogin\Actions\SendMagicLinkAction;
TextInput::make('email')
->email()
->hintAction(SendMagicLinkAction::make())
SendMagicLinkAction::make()->asSlideover()
Send magic links using your own Mailable class instead of the default:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CustomMagicLinkMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public string $url,
public int $expiryMinutes,
) {}
public function envelope(): Envelope
{
return new Envelope(
subject: 'Your Secure Login Link',
);
}
public function content(): Content
{
return new Content(
markdown: 'emails.magic-link',
with: [
'url' => $this->url,
'expiryMinutes' => $this->expiryMinutes,
],
);
}
}
{{-- resources/views/emails/magic-link.blade.php --}}
<x-mail::message>
# Hello!
You requested a secure login link. Click the button below to sign in:
<x-mail::button :url="$url">
Sign In Now
</x-mail::button>
This link will expire in {{ $expiryMinutes }} minutes.
If you did not request this link, no action is needed.
Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
FilamentPasswordlessLoginPlugin::make()
->mailable(\App\Mail\CustomMagicLinkMail::class)
Use your own Laravel Notification class instead of the default:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class CustomMagicLinkNotification extends Notification
{
use Queueable;
public function __construct(
public string $url,
public int $expiryMinutes,
public array $metadata = [],
) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('Your Login Link — ' . config('app.name'))
->greeting('Hello ' . ($notifiable->name ?? '') . '!')
->line('Click the button below to sign in securely.')
->action('Sign In', $this->url)
->line('This link expires in ' . $this->expiryMinutes . ' minutes.')
->line('If you did not request this, please ignore this email.');
}
}
FilamentPasswordlessLoginPlugin::make()
->notification(\App\Notifications\CustomMagicLinkNotification::class)
Note: Your notification class receives
$url,$expiryMinutes, and$metadatain the constructor — same signature as the base package's default notification.
The plugin registers a full Filament resource for managing magic login tokens at /admin/magic-login-tokens (configurable).
The stats and chart widgets are displayed as header widgets on the token list page (not on the dashboard):
FilamentPasswordlessLoginPlugin::make()
->resource() // Enable (default)
->resourceSlug('magic-links') // Custom URL: /admin/magic-links
->navigationGroup('Security') // Custom sidebar group
->navigationIcon('heroicon-o-key') // Custom sidebar icon
->navigationSort(50) // Custom sort order
->canCreateTokens(false) // Hide create button
->canDeleteTokens(false) // Hide delete actions
->statsWidget() // Enable stats (default)
->chartsWidget() // Enable charts (default)
->chartDays(60) // Show 60 days of data
Navigation group and label are pulled from language files by default. Publish and edit:
php artisan vendor:publish --tag=filament-passwordless-login-lang
Then edit lang/vendor/filament-passwordless-login/en/filament.php:
return [
'navigation_group' => 'Security', // Sidebar group name
'navigation_label' => 'Login Links', // Sidebar item label
'resource_label' => 'Login Link', // Singular label
'resource_plural_label' => 'Login Links', // Plural label
// ... all other strings
];
Note: The fluent API (
->navigationGroup('...')) takes priority over language files when both are set.
8 languages included out of the box:
| Language | Code | Example Navigation Group |
|---|---|---|
| English | en |
Authentication |
| Spanish | es |
Autenticación |
| French | fr |
Authentification |
| German | de |
Authentifizierung |
| Dutch | nl |
Authenticatie |
| Arabic | ar |
المصادقة |
| Hindi | hi |
प्रमाणीकरण |
| Portuguese | pt |
Autenticação |
All strings are translatable — login page text, action labels, modal headings, resource columns, widget headings, status badges, navigation labels, and filter labels.
Create a new file at lang/vendor/filament-passwordless-login/{locale}/filament.php and translate all keys from the English file.
Published to config/filament-passwordless-login.php:
return [
// Login page settings
'login_page' => [
'enabled' => true,
'show_password_login_link' => true,
],
// Login action settings
'login_action' => [
'enabled' => false,
'icon' => 'heroicon-m-link',
'color' => 'primary',
'slideover' => false,
'width' => 'md',
],
// Resource settings
'resource' => [
'enabled' => true,
'slug' => 'magic-login-tokens',
'can_create' => true,
'can_delete' => true,
],
// Widget settings
'widgets' => [
'stats_enabled' => true,
'charts_enabled' => true,
'chart_days' => 30,
],
];
The Filament plugin uses spykapps/passwordless-login under the hood. Configure the base package in config/passwordless-login.php:
return [
// User model
'user_model' => \App\Models\User::class,
'email_column' => 'email',
// Token settings
'token' => [
'length' => 32,
'hash_algorithm' => 'sha256',
],
// Link expiry
'expiry_minutes' => 15,
// Max uses per link (null = unlimited)
'max_uses' => 1,
// Bot detection for email clients (Outlook, Apple Mail, SafeLinks, etc.)
'bot_detection' => [
'enabled' => true,
'strategy' => 'both', // 'confirmation_page', 'javascript', or 'both'
],
// Rate limiting
'throttle' => [
'enabled' => true,
'max_attempts' => 5,
'decay_minutes' => 10,
],
// Redirect after login
'redirect' => [
'on_success' => '/admin',
'on_failure' => '/admin/login',
],
// Security
'security' => [
'invalidate_previous' => true,
'invalidate_on_login' => true,
'ip_binding' => false,
'user_agent_binding' => false,
],
];
See the spykapps/passwordless-login README for the full list of configuration options.
FilamentPasswordlessLoginPlugin::make()
->loginPage()
->showPasswordLoginLink(false)
->mailable(\App\Mail\BrandedMagicLink::class)
FilamentPasswordlessLoginPlugin::make()
->loginPage(false) // Keep default Filament login
->loginAction() // Enable login action
->loginActionPosition(FilamentPasswordlessLoginActionPosition::EmailFieldHint)
->loginActionIcon('heroicon-m-envelope')
->loginActionColor('success')
FilamentPasswordlessLoginPlugin::make()
->loginPage(false) // Keep default Filament login
->loginAction() // Enable login action
->loginActionPosition(FilamentPasswordlessLoginActionPosition::LoginFormEndButton)
->slideover() // Open as slide-over
FilamentPasswordlessLoginPlugin::make()
->loginPage()
->showPasswordLoginLink()
->redirectUrl('/admin/dashboard')
->failureUrl('/admin/login')
->loginAction()
->loginActionPosition(FilamentPasswordlessLoginActionPosition::EmailFieldHint)
->slideover()
->mailable(\App\Mail\CustomMagicLink::class)
->resource()
->navigationGroup('Security')
->navigationIcon('heroicon-o-shield-check')
->navigationSort(50)
->statsWidget()
->chartsWidget()
->chartDays(90)
FilamentPasswordlessLoginPlugin::make()
->loginPage(false)
->loginAction(false)
->resource()
->statsWidget()
->chartsWidget(false)
use SpykApp\FilamentPasswordlessLogin\Enums\FilamentPasswordlessLoginActionPosition;
FilamentPasswordlessLoginActionPosition::EmailFieldHint // Hint icon on email field
FilamentPasswordlessLoginActionPosition::LoginFormEndButton // Button after login form
The MIT License (MIT). Please see License File for more information.