Skip to content

Filament v4 Schema-Based Forms: Unified Schema API and Infolist Patterns

Filament v4 replaces scattered form/infolist definitions with a single Schema API. Learn how unified schemas, conditional visibility, and reusable schema components reduce duplication in real Filament panels.

Published · Updated · 3 min read

Filament v4 Schema-Based Forms: Unified Schema API and Infolist Patterns

Filament v4 Schema-Based Forms and Infolists

Filament v4 introduced one of its most architecturally significant changes: a unified Schema API that collapses the previously separate form and infolist definition surfaces into a single, composable layer. If you have built Filament v3 resources, you know the friction — form(Form $form) and infolist(Infolist $infolist) each demanded their own component trees, leading to duplicated field definitions for read and write views.

Filament v4 solves this by making Schema the first-class citizen.

The Old Friction

In v3, a TextInput in a form and a TextEntry in an infolist were entirely separate classes with separate APIs. Keeping them in sync was a maintenance burden:

// v3 — two separate definitions, easy to drift
public static function form(Form $form): Form
{
    return $form->schema([
        TextInput::make('email')->email()->required(),
    ]);
}

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

The v4 Unified Schema

Filament v4 introduces Schema components that are context-aware: the same component tree renders as editable inputs inside a form context and as read-only entries inside an infolist context. You define the schema once and let Filament resolve the correct renderer.

use Filament\Schema\Schema;
use Filament\Schema\Components\TextInput;
use Filament\Schema\Components\Section;

public static function schema(Schema $schema): Schema
{
    return $schema->components([
        Section::make('Identity')
            ->schema([
                TextInput::make('name')
                    ->required()
                    ->maxLength(120),

                TextInput::make('email')
                    ->email()
                    ->required(),
            ]),
    ]);
}

Both the EditRecord and ViewRecord pages consume this single schema() method. The framework injects the correct context — form or infolist — at render time.

Reusable Schema Components

The real power emerges when you extract schemas into dedicated classes. This is the pattern I reach for on any resource with more than a handful of fields:

namespace App\Filament\Schemas;

use Filament\Schema\Schema;
use Filament\Schema\Components\TextInput;
use Filament\Schema\Components\Select;

class UserIdentitySchema
{
    public static function make(): array
    {
        return [
            TextInput::make('name')->required(),
            TextInput::make('email')->email()->required(),
            Select::make('role')
                ->options(Role::class)
                ->required(),
        ];
    }
}

Then compose it anywhere:

public static function schema(Schema $schema): Schema
{
    return $schema->components([
        ...UserIdentitySchema::make(),
        ...BillingAddressSchema::make(),
    ]);
}

This is clean, testable, and trivially shareable across resources, modals, and wizard steps.

Conditional Visibility

Conditional logic works identically whether the schema is rendered as a form or infolist:

TextInput::make('vat_number')
    ->visible(fn (Get $get): bool => $get('is_business') === true)
    ->required(fn (Get $get): bool => $get('is_business') === true),

The Get closure resolves live state in form context and static record state in infolist context — no branching required in your schema definition.

Practical Migration Note

If you are migrating from v3, the key shift is:

  1. Replace form() + infolist() with a single schema() method.
  2. Swap TextEntry, ImageEntry, etc. for their unified Schema\Components equivalents.
  3. Move shared field groups into dedicated schema classes immediately — do not let them grow inline.

Not every v3 component has a direct v4 unified equivalent yet; check the official changelog before assuming a component is context-aware.

Takeaways

  • Filament v4's unified Schema API eliminates the form/infolist duplication that plagued v3 resources.
  • A single schema() method serves both edit and view contexts via context injection.
  • Extracting schemas into plain PHP classes (UserIdentitySchema::make()) is the cleanest reuse pattern.
  • Conditional visibility with Get works uniformly across both contexts.
  • Migrate incrementally: start with new resources, then refactor existing ones once you verify component parity.

Frequently asked questions

Can I still use separate form() and infolist() methods in Filament v4?

Yes. Filament v4 is backward-compatible in this regard — you can keep separate definitions. The unified schema() method is opt-in, but adopting it removes duplication and is the recommended approach for new resources.

Do all Filament v3 components have unified equivalents in v4?

Not all v3 components have been ported to the unified Schema namespace yet. Check the Filament v4 changelog and component reference before assuming a component is context-aware; some still require separate form/infolist definitions.

How do I share a schema between a resource and a modal action in Filament v4?

Extract the schema into a static method on a dedicated class and spread the result into both the resource schema() and the action's schema() call using the splat operator (...MySchema::make()). Both contexts accept the same component array.

Mohamed Said

About the author

Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

About GitHub LinkedIn WhatsApp Email Address My CV

Have a technical challenge?

Tell me what you’re building. I reply within two working days.

Discuss your project