Christopher Pitt Software Developer

Using Forms on Custom Filament Pages

Last updated on 10th April 2024

Minutes after posting this, a friend pointed me towards docs that cover some of this topic. Perhaps I'll make a PR to improve these so that they also cover form actions...

Let me share a little secret with you, about Filament. You probably know you can make custom pages, but what’s not immediately obvious is that you can still use Filament’s form components without having to butcher a resource class or reinvent it from scratch. This isn’t well documented, but at least it’s easy once you know how…

The first step is to create a custom page. Filament includes a command for this:

php artisan make:filament-page CustomPage

You can associate this with an existing resource, but it’s really not required that you do so. After running this command, the new page should appear in the sidebar.

If you get an error about a missing route, it’s likely that you have cached routes. Run php artisan route:clear, and the error should go away.

When I first tried this, I assumed I could just copy over public static function form(Form $form): Form; from one of my resource classes, but this won’t work. Instead, we need to implement a couple of interfaces. Here’s what the generated class looks like:

namespace App\Filament\Pages;

use Filament\Pages\Page;

class CustomPage extends Page
{
    protected static ?string $navigationIcon = 'heroicon-o-document-text';

    protected static string $view = 'filament.pages.custom-page';
}

This is from app/Filament/Pages/CustomPage.php

And, here’s what we need to add before forms will work:

namespace App\Filament\Pages;

use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Pages\Concerns\InteractsWithFormActions;
use Filament\Pages\Page;

class CustomPage extends Page implements HasActions, HasForms
{
    use InteractsWithFormActions;
    use InteractsWithForms;

    protected static ?string $navigationIcon = 'heroicon-o-document-text';

    protected static string $view = 'filament.pages.custom-page';

    protected function getFormSchema(): array
    {
        return [
            // TODO
        ];
    }

    public function getFormActions(): array
    {
        return [
            // TODO
        ];
    }
}

This is from app/Filament/Pages/CustomPage.php

These interfaces require methods with slight different names to what we are used to in resources. There’s work to do in the view, before we add form components and actions to these empty methods. When we created the custom page, Filament also created an empty view:

<x-filament-panels::page>

</x-filament-panels::page>

This is from resources/views/filament/pages/custom-page.blade.php

I had to dig deep in the vendor folder to figure out what to put in this view. Fortunately, we don’t have to customise it for each custom page. You can point every custom page — that you want to use Filament forms in — to the same template:

<x-filament-panels::page>
    <x-filament-panels::form
        id="form"
        wire:key="{{ 'forms.' . $this->getFormStatePath() }}"
    >
        {{ $this->form }}

        <x-filament-panels::form.actions
            :actions="$this->getCachedFormActions()"
            :full-width="$this->hasFullWidthFormActions()"
        />
    </x-filament-panels::form>
</x-filament-panels::page>

This is from resources/views/filament/pages/custom-page.blade.php

This is loosely based on the view code for a resources’s create view. Now, let’s add some form components and actions to the page class:

public string $name = '';

protected function getFormSchema(): array
{
    return [
        TextInput::make('name'),
    ];
}

public function getFormActions(): array
{
    return [
        Action::make('greet')
            ->action(function () {
                Notification::make()
                    ->title(__('Hello ' . $this->name))
                    ->success()
                    ->send();
            }),
    ];
}

This is from app/Filament/Pages/CustomPage.php

Custom pages are just Livewire components, and Filament knows well enough how to match form fields with the public properties that their state should be stored in. You can create as many custom actions as you like, and put the code that should be run on click/submit right there.

It’s so easy, once you know the secret interfaces, traits, and view code you need to use. I hope you spend less time figuring this out than I did, and more time appreciating how cool (if a little undocumented) Filament really is.