Filament: Customize Auth Emails like Reset Password or Verification

Filament offers authentication features like registration, password reset, with sending emails automatically. How to customize those emails or change texts inside them?

In this tutorial, we will show how to add user's name after the "Hello" greeting in those emails.

This example will be about the "Forgot Password" email, but the same logic applies to all the other Filament Auth email classes.

This change is actually quite complicated, especially for those who are used that almost everything in Filament is configurable easily.

The plan is this:

  1. Create a new Laravel Notification class, adding logic to include User name
  2. Create a new Filament Page that extends the default Reset Password page
  3. In that Page, replace the old Notification class with our new Notification class
  4. Assign that Page to Filament in the global Admin Panel Provider

Create Laravel Notification Class

First, we need to create a new Notification class, which we will use to send a reset password email. The code of that class will be almost identical to the default Laravel ResetPassword class that Filament uses under the hood, we just need to include the user name there.

php artisan make:notification ResetPasswordNotification

The Notification class will accept a token. Inside that class, we need to generate a reset URL.

app/Notifications/ResetPasswordNotification.php:

use Filament\Facades\Filament;
use Illuminate\Support\Facades\Lang;
 
class ResetPasswordNotification extends Notification
{
use Queueable;
 
public function __construct(private readonly string $token)
{}
 
public function via(object $notifiable): array
{
return ['mail'];
}
 
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject(Lang::get('Reset Password Notification'))
->greeting(Lang::get('Hello') . " {$notifiable->name},")
->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::get('Reset Password'), $this->resetUrl($notifiable))
->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further action is required.'));
}
 
protected function resetUrl(mixed $notifiable): string
{
return Filament::getResetPasswordUrl($this->token, $notifiable);
}
 
}

New Filament Reset Password Page

Next, Filament allows you to customize authentication features by passing your Filament page to the feature. Let's create a Filament page.

php artisan make:filament-page Auth/RequestPasswordReset

This page must extend the original RequestPasswordReset page from the Filament

app/Filament/Pages/Auth/RequestPasswordReset.php:

use Filament\Pages\Page;
use Filament\Pages\Auth\PasswordReset\RequestPasswordReset as BaseRequestPasswordReset;
class RequestPasswordReset extends Page
class RequestPasswordReset extends BaseRequestPasswordReset
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.auth.request-password-reset2';
}

Replace the Filament Notification with Our Class

Now, we need to overwrite the method where the Notification is sent. You can check the base class to see which method to overwrite. In this case, it is the request() method.

See the highlighted code line: we use the same class name, but replace the Filament class with our own Notification class in the use section on top.

app/Filament/Pages/Auth/RequestPasswordReset.php:

use Exception;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\Password;
use Illuminate\Contracts\Auth\CanResetPassword;
use App\Notifications\ResetPasswordNotification;
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Filament\Notifications\Auth\ResetPassword as ResetPasswordNotification;
use Filament\Pages\Auth\PasswordReset\RequestPasswordReset as BaseRequestPasswordReset;
 
class RequestPasswordReset extends BaseRequestPasswordReset
{
public function request(): void
{
try {
$this->rateLimit(2);
} catch (TooManyRequestsException $exception) {
Notification::make()
->title(__('filament-panels::pages/auth/password-reset/request-password-reset.notifications.throttled.title', [
'seconds' => $exception->secondsUntilAvailable,
'minutes' => ceil($exception->secondsUntilAvailable / 60),
]))
->body(array_key_exists('body', __('filament-panels::pages/auth/password-reset/request-password-reset.notifications.throttled') ?: []) ? __('filament-panels::pages/auth/password-reset/request-password-reset.notifications.throttled.body', [
'seconds' => $exception->secondsUntilAvailable,
'minutes' => ceil($exception->secondsUntilAvailable / 60),
]) : null)
->danger()
->send();
 
return;
}
 
$data = $this->form->getState();
$status = Password::broker(Filament::getAuthPasswordBroker())->sendResetLink(
$data,
function (CanResetPassword $user, string $token): void {
if (! method_exists($user, 'notify')) {
$userClass = $user::class;
throw new Exception("Model [{$userClass}] does not have a [notify()] method.");
}
 
$notification = new ResetPasswordNotification($token);
$notification->url = Filament::getResetPasswordUrl($token, $user);
$user->notify($notification);
},
);
 
if ($status !== Password::RESET_LINK_SENT) {
Notification::make()
->title(__($status))
->danger()
->send();
 
return;
}
 
Notification::make()
->title(__($status))
->success()
->send();
 
$this->form->fill();
}
}

Assign Filament Page in Panel Provider

The last thing left is to use this custom Filament page to reset the password in the Panel Provider.

app/Providers/Filament/AdminPanelProvider.php:

use App\Filament\Pages\Auth\RequestPasswordReset;
 
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->default()
->id('admin')
->path('admin')
->login()
->passwordReset()
->passwordReset(RequestPasswordReset::class)
 
// ...
}
}

When the password reset email is sent, the user's name should be in the email.

As mentioned earlier, you can apply the same process from this tutorial to email verification after the user registration.


If you want more Filament examples, you can find more real-life projects on our FilamentExamples.com.

avatar

Thanks. I've implemented this exactly into my "customer" panel (not "admin). But I get the following error:

local.ERROR: Route [filament.admin.auth.password-reset.reset] not defined. {"exception":"[object] (Symfony\\Component\\Routing\\Exception\\RouteNotFoundException(code: 0): Route [filament.admin.auth.password-reset.reset] not defined. at /Users/nils/Herd/activephysioshop/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php:477)

and I can't figure out, what this error causes in the custom code. I haven't set this route anywhere myself (or can't remember that I've done this 😉 )

avatar

I got it: In app/Notifications/ResetPasswordNotification.php:

protected function resetUrl(mixed $notifiable): string { return Filament::getPanel('customer')->getResetPasswordUrl($this->token, $notifiable); }

where 'customer' is the panel-ID.

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1057 lessons, total 42 h 44 min)
  • 79 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials