Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API
#filament #laravel #filament-v4 #admin-panel

Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API

3 min read Mohamed Said Mohamed Said

Why Filament v4 Rewrote the Component Model

In Filament v3 you maintained two parallel trees: form(Form $form) returned $form->schema([...]) and infolist(Infolist $infolist) returned $infolist->schema([...]). The components were different classes even when they displayed the same data — a TextInput for editing, a TextEntry for viewing. Filament v4 collapses this into a unified Schema API where a single component tree can render in both contexts, and dedicated entry components are first-class citizens alongside field components.


The Schema Component Tree

Every layout wrapper — Section, Grid, Tabs, Fieldset — now lives under Filament\Schemas\Components\ and is shared between forms and infolists. You import them once and use them everywhere.

use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;

public static function form(Form $form): Form
{
    return $form->schema([
        Section::make('Identity')
            ->schema([
                Grid::make(2)->schema([
                    TextInput::make('name')->required(),
                    TextInput::make('email')->email()->required(),
                ]),
            ]),
    ]);
}

public static function infolist(Infolist $infolist): Infolist
{
    return $infolist->schema([
        Section::make('Identity')
            ->schema([
                Grid::make(2)->schema([
                    TextEntry::make('name'),
                    TextEntry::make('email'),
                ]),
            ]),
    ]);
}

The Section and Grid imports are identical. Only the leaf components differ.


Extracting a Shared Schema Method

Because layout wrappers are now the same class, you can extract the skeleton into a static helper and swap only the leaves.

private static function identitySchema(array $fields): array
{
    return [
        Section::make('Identity')
            ->schema([
                Grid::make(2)->schema($fields),
            ]),
    ];
}

public static function form(Form $form): Form
{
    return $form->schema(self::identitySchema([
        TextInput::make('name')->required(),
        TextInput::make('email')->email()->required(),
    ]));
}

public static function infolist(Infolist $infolist): Infolist
{
    return $infolist->schema(self::identitySchema([
        TextEntry::make('name'),
        TextEntry::make('email'),
    ]));
}

This pattern eliminates the structural duplication that plagued v3 resources with large schemas.


Schema Components in Custom Pages and Widgets

Custom pages that previously called $this->form->fill() now use HasForms or HasInfolists traits alongside the schema builder. The $this->form(...) and $this->infolist(...) calls accept the same unified component classes.

use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Schemas\Components\Section;

class EditProfilePage extends Page
{
    use InteractsWithForms;

    public function form(Form $form): Form
    {
        return $form
            ->schema([
                Section::make()->schema([
                    TextInput::make('bio')->columnSpanFull(),
                ]),
            ])
            ->statePath('data');
    }
}

What Actually Breaks When Upgrading

Namespace changes

Any import of Filament\Forms\Components\Section or Filament\Forms\Components\Grid must move to Filament\Schemas\Components\. A project-wide find-and-replace handles most of it.

->columns() on Section

In v3 you called ->columns(2) on Section directly. In v4 you wrap children in Grid::make(2) instead. The old shorthand still works as a compatibility shim in early v4 releases, but the canonical approach is explicit Grid.

Custom field getChildComponents()

If you built custom layout components by extending Filament\Forms\Components\Component, the base class has moved. Extend Filament\Schemas\Components\Component instead and implement getChildComponents() as before.


Takeaways

  • Layout components (Section, Grid, Tabs) are now shared across forms and infolists under Filament\Schemas\Components\.
  • Leaf components (TextInput, TextEntry) remain context-specific but sit inside the same tree.
  • Extracting a shared schema skeleton method removes structural duplication across form() and infolist().
  • The main upgrade cost is namespace replacement and swapping ->columns() shortcuts for explicit Grid wrappers.
  • Custom layout components must extend the new base class in Filament\Schemas\Components\.

Found this useful?

Frequently Asked Questions

3 questions
Q01 Can I use a single component for both editing and viewing in Filament v4?
Not for leaf components — TextInput is still edit-only and TextEntry is view-only. The unification applies to layout wrappers like Section and Grid, which are now the same class in both contexts.
Q02 Do I need to update every resource immediately after upgrading to Filament v4?
Filament v4 ships compatibility shims for the most common v3 form namespace imports, so many resources continue to work. However, the shims are not guaranteed across minor releases, so migrating namespaces early is strongly recommended.
Q03 Where should custom layout components extend from in Filament v4?
Extend Filament\Schemas\Components\Component instead of the old Filament\Forms\Components\Component. The API for getChildComponents() and childComponents() remains the same.

Continue reading

More Articles

View all