Courses

[Mini-course] Filament: Visual Customizations

Render Hooks: Add Code in Forms, Header/Footer/Sidebar and More

Filament Render Hooks are a great way to add additional information or actions in various places throughout the Filament panel. Let's examine some of the Render Hooks in a practical way.


1. Login/Register/Auth Forms: Before and After

When creating an application locally, you often need to log in with some user for testing. With the help of the spatie/laravel-login-link package and Filament Render Hooks, you can easily add "quick login" links. We have a regular login page:

Using the AUTH_LOGIN_FORM_BEFORE Render Hook, we can add a link above the login form.

app/Providers/AppServiceProvider.php:

use Filament\View\PanelsRenderHook;
use Illuminate\Support\Facades\Blade;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::AUTH_LOGIN_FORM_BEFORE,
fn (): string => Blade::render('@env(\'local\')<x-login-link />@endenv'),
);
}
}

And we have the result:

Or, using the AUTH_LOGIN_FORM_AFTER, the link can be added below the form.

app/Providers/AppServiceProvider.php:

use Filament\View\PanelsRenderHook;
use Illuminate\Support\Facades\Blade;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::AUTH_LOGIN_FORM_AFTER,
fn (): string => Blade::render('@env(\'local\')<x-login-link />@endenv'),
);
}
}

Another example is the AUTH_LOGIN_FORM_AFTER hook, which can also be handy when adding social login links.

app/Providers/AppServiceProvider.php:

use Illuminate\View\View;
use Filament\View\PanelsRenderHook;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::AUTH_LOGIN_FORM_AFTER,
fn (): View => view('socialite-logins'),
);
}
}

In the socialite-logins.blade.php View, we add the social login buttons.

resources/views/socialite-logins.blade.php:

<x-filament::button
color="gray"
href="https://filamentphp.com"
tag="a"
>
Login using GitHub
</x-filament::button>

The same logic can be used for Register and other Auth pages:

  • PanelsRenderHook::AUTH_PASSWORD_RESET_REQUEST_FORM_AFTER - After password reset request form
  • PanelsRenderHook::AUTH_PASSWORD_RESET_REQUEST_FORM_BEFORE - Before the password reset request form
  • PanelsRenderHook::AUTH_PASSWORD_RESET_RESET_FORM_AFTER - After password reset form
  • PanelsRenderHook::AUTH_PASSWORD_RESET_RESET_FORM_BEFORE - Before password reset form
  • PanelsRenderHook::AUTH_REGISTER_FORM_AFTER - After register form
  • PanelsRenderHook::AUTH_REGISTER_FORM_BEFORE - Before registering the form

2. Footer or Sidebar Footer

You might want to add a footer with copyright text in your application, especially if it's a SaaS application. You can easily render a view using the FOOTER render hook.

app/Providers/AppServiceProvider.php:

use Illuminate\View\View;
use Filament\View\PanelsRenderHook;
use Illuminate\Support\Facades\Blade;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::FOOTER,
fn (): View => view('footer'),
);
}
}

In the View, you would add the content of a footer.

resources/views/footer.blade.php:

<footer class="fixed bottom-0 left-0 z-20 w-full p-4 bg-white border-t border-gray-200 shadow md:flex md:items-center md:justify-between md:p-6 dark:bg-gray-800 dark:border-gray-600">
<span class="text-sm text-gray-500 sm:text-center dark:text-gray-400">
&copy 2024 Laravel Daily &middot; <a href="https://laraveldaily.com/about-project">About LaravelDaily</a>
</span>
</footer>

Another similar SIDEBAR_FOOTER Render Hook adds a footer at the bottom of the sidebar.

app/Providers/AppServiceProvider.php:

use Illuminate\View\View;
use Filament\View\PanelsRenderHook;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::SIDEBAR_FOOTER,
fn (): View => view('footer'),
);
}
}


3. Top Bar: Global Search or User Menu

At the topbar, you can add some indicators of quick actions using the GLOBAL_SEARCH_BEFORE or GLOBAL_SEARCH_AFTER and USER_MENU_BEFORE or USER_MENU_AFTER hooks.

For example, the pxlrbt/filament-environment-indicator package uses the GLOBAL_SEARCH_BEFORE render hook to show the environment indicator by returning a View.

This indicator can be helpful when hoping between environments in your application.

use Filament\Contracts\Plugin;
use Filament\Panel;
use Illuminate\Support\Facades\View;
 
class EnvironmentIndicatorPlugin implements Plugin
{
// ...
 
public function register(Panel $panel): void
{
$panel->renderHook('panels::global-search.before', function () {
if (! $this->evaluate($this->visible)) {
return '';
}
 
if (! $this->evaluate($this->showBadge)) {
return '';
}
 
return View::make('filament-environment-indicator::badge', [
'color' => $this->getColor(),
'environment' => ucfirst(app()->environment()),
]);
});
 
// ...
}
 
// ...
}

Another topbar example is from a package awcodes/filament-quick-create, which by default uses the USER_MENU_BEFORE Render Hook and renders a Livewire component.

use Closure;
use Filament\Contracts\Plugin;
use Filament\Panel;
use Filament\Support\Concerns\EvaluatesClosures;
use Filament\View\PanelsRenderHook;
use Illuminate\Support\Facades\Blade;
 
class QuickCreatePlugin implements Plugin
{
// ...
 
public function register(Panel $panel): void
{
$panel
->renderHook(
name: $this->getRenderHook(),
hook: fn (): string => Blade::render("@livewire('quick-create-menu')")
);
}
 
// ...
 
public function getRenderHook(): string
{
return $this->evaluate($this->renderUsingHook) ?? PanelsRenderHook::USER_MENU_BEFORE;
}
 
// ...
}


4. Global Notification on Top of Main Content

You might need to add some notifications so all users can see them. For such cases, the PAGE_START Render Hook can be used.

app/Providers/AppServiceProvider.php:

use Illuminate\View\View;
use Filament\View\PanelsRenderHook;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::PAGE_START,
fn (): View => view('notification'),
);
}
}

You can add additional checks to the View file. For example, if maintenance is done, don't show the message.

resources/views/notification.blade.php:

<div class="mt-3 rounded-md px-4 py-2 bg-primary-100 text-danger-600">
Application will be in maintenance from <b>2024-09-01 18:00 UTC</b> to <b>2024-09-01 20:00 UTC</b>
</div>


5. On Top/Bottom of the Sidebar

You can add widgets and additional information to the sidebar before or after navigation.

For example, if you are building something similar to Dropbox, you might want to show available storage and how much is used.

app/Providers/AppServiceProvider.php:

use Illuminate\View\View;
use Filament\View\PanelsRenderHook;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::SIDEBAR_NAV_START,
fn (): View => view('storage-info'),
);
}
}

In the View, show what is needed.

resources/views/storage-info.blade.php:

<div class="flex flex-col items-center justify-center">
<div class="!z-5 relative flex h-full w-full flex-col rounded-xl bg-white bg-clip-border p-4 shadow-3xl shadow-shadow-500 dark:!bg-navy-800 dark:text-white dark:shadow-none">
<div class="mb-auto flex flex-col items-center justify-center">
<div class="flex items-center justify-center rounded-full text-5xl font-bold text-brand-500 dark:text-white">
@svg('heroicon-o-cloud-arrow-up', ['class' => 'h-12 w-12'])
</div>
<h4 class="mt-3 mb-px text-2xl font-bold text-navy-700 dark:text-white">
Your storage
</h4>
</div>
<div class="flex flex-col">
<div class="flex justify-between">
<p class="text-sm font-medium text-gray-600">25.6 GB</p>
<p class="text-sm font-medium text-gray-600">50 GB</p>
</div>
<div class="mt-2 flex h-3 w-full items-center rounded-full bg-lightPrimary dark:!bg-navy-700">
<span class="h-full w-1/2 rounded-full bg-info-500"></span>
</div>
</div>
</div>
</div>

You can also show it below the navbar.

use Illuminate\View\View;
use Filament\View\PanelsRenderHook;
use Filament\Support\Facades\FilamentView;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
PanelsRenderHook::SIDEBAR_NAV_END,
fn (): View => view('storage-info'),
);
}
}


6. Table Render Hooks Examples

Filament tables also have some hooks. For example, you can add actions to the table near the search bar or filters.

Let's say we want to add a Feedback button, a Livewire component that will open a modal to leave feedback.

app/Livewire/FeedbackCreate.php:

use Livewire\Component;
use App\Models\Feedback;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Components\Textarea;
use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Actions\Concerns\InteractsWithActions;
 
class FeedbackCreate extends Component implements HasForms, HasActions
{
use InteractsWithForms;
use InteractsWithActions;
 
public function createAction(): Action
{
return CreateAction::make()
->model(Feedback::class)
->label('Feedback')
->link()
->extraAttributes([
'class' => 'px-3',
])
->modalSubmitActionLabel('Send Feedback')
->modalHeading('Feedback')
->icon('heroicon-o-megaphone')
->createAnother(false)
->color('info')
->form([
Select::make('type')
->options([
'bug' => 'Bug',
'feature' => 'Feature',
'question' => 'Question',
'comment' => 'Comment',
])
->required(),
Textarea::make('feedback')
->label('Feedback')
->autosize()
->rows(6)
->minLength(3)
->maxLength(10000)
->required(),
])
->using(function (array $data): Feedback {
return auth()->user()->feedback()->create($data);
})
->successNotificationTitle('Feedback Received!');
}
 
public function render(): string
{
return <<<'HTML'
<div>
{{ $this->createAction() }}
 
<x-filament-actions::modals />
</div>
HTML;
}
}

Now that we have a Livewire component using a table render hook, we can show it. In this case, we scoped this hook only for the list users page, which means that this render hook will be added only to the specified page.

app/Providers/AppServiceProvider.php:

use Illuminate\Support\Facades\Blade;
use Filament\Support\Facades\FilamentView;
use Filament\Tables\View\TablesRenderHook;
use App\Filament\Resources\UserResource\Pages\ListUsers;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
FilamentView::registerRenderHook(
TablesRenderHook::TOOLBAR_TOGGLE_COLUMN_TRIGGER_AFTER,
fn (): string => Blade::render('@livewire(\'feedback-create\')'),
scopes: ListUsers::class
);
}
}

If you want to show this action on every table, you can use a different hook, TOOLBAR_END, which will place the action button in the same place, or you can ignore the scope of the hook.


More Render Hooks are available, so you can explore the whole list and experiment with your projects.

avatar

Thanks! Nice work! I had already used this, but the course is very useful to get extra ideas and usage examples.