FilamentPHP Schemas
FilamentPHP Schemas: Building Server-Driven UIs in PHP
Master FilamentPHP v4's revolutionary Schemas system. Learn to combine forms, infolists, and actions into unified interfaces without writing JavaScript.

Hoceine El Idrissi
Full Stack Developer
FilamentPHP Schemas: Building Server-Driven UIs in PHP
Schemas are FilamentPHP v4's most architectural innovation. They unify forms, infolists, tables, and actions into a single, composable component system—all controlled from PHP.
What Are Schemas?
Schemas are a unified component architecture that allows you to mix and match different component types in a single view:
use Filament\Schemas\Schema;
use Filament\Schemas\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Actions\Action;
public function schema(Schema $schema): Schema
{
return $schema->components([
Section::make('User Profile')
->schema([
// Editable field
TextInput::make('name')->required(),
// Read-only display
TextEntry::make('email'),
// Action button
Action::make('verify')
->button()
->action(fn () => $this->verify()),
]),
]);
}
Why Schemas Matter
Before Schemas (v3)
You needed separate classes and views:
// UserResource/Pages/EditUser.php - Form
// UserResource/Pages/ViewUser.php - Infolist
// Separate action modals
// Different component APIs
With Schemas (v4)
Everything in one place:
// One schema, multiple component types
public function profileSchema(Schema $schema): Schema
{
return $schema->components([
$this->editableSection(),
$this->readOnlyStats(),
$this->actionButtons(),
]);
}
Core Schema Components
Section
The primary container for grouping:
Section::make('Account Details')
->description('Manage your account settings')
->icon('heroicon-o-user')
->collapsible()
->schema([
TextInput::make('username'),
TextInput::make('email')->email(),
]);
Grid
Responsive layouts:
Grid::make(3) // 3 columns
->schema([
TextInput::make('first_name')->columnSpan(1),
TextInput::make('last_name')->columnSpan(1),
TextInput::make('email')->columnSpan(1),
]);
Tabs
Organized content:
Tabs::make('Settings')
->tabs([
Tab::make('Profile')
->icon('heroicon-o-user')
->schema([
TextInput::make('name'),
FileUpload::make('avatar'),
]),
Tab::make('Security')
->icon('heroicon-o-lock-closed')
->schema([
TextInput::make('password')->password(),
Toggle::make('two_factor'),
]),
]);
Mixing Component Types
The real power is combining forms, infolists, and actions:
public function orderSchema(Schema $schema): Schema
{
return $schema->components([
// Read-only order info
Section::make('Order Information')
->schema([
TextEntry::make('order_number')
->copyable(),
TextEntry::make('created_at')
->dateTime(),
BadgeEntry::make('status')
->color(fn (string $state) => match ($state) {
'pending' => 'warning',
'processing' => 'info',
'completed' => 'success',
'cancelled' => 'danger',
}),
]),
// Editable notes
Section::make('Internal Notes')
->schema([
Textarea::make('notes')
->rows(3),
]),
// Action buttons
Actions::make([
Action::make('process')
->color('info')
->requiresConfirmation()
->action(fn () => $this->record->process()),
Action::make('cancel')
->color('danger')
->requiresConfirmation()
->action(fn () => $this->record->cancel()),
]),
]);
}
Building Custom Pages with Schemas
Dashboard Example
class Dashboard extends Page
{
protected static string $view = 'filament.pages.dashboard';
public function schema(Schema $schema): Schema
{
return $schema->components([
// Stats widgets
Grid::make(4)->schema([
$this->statCard('Total Users', User::count(), 'heroicon-o-users'),
$this->statCard('Revenue', '$' . number_format(Order::sum('total')), 'heroicon-o-currency-dollar'),
$this->statCard('Orders', Order::count(), 'heroicon-o-shopping-cart'),
$this->statCard('Products', Product::count(), 'heroicon-o-cube'),
]),
// Recent activity
Section::make('Recent Orders')
->schema([
RepeatableEntry::make('recentOrders')
->schema([
TextEntry::make('order_number'),
TextEntry::make('customer.name'),
TextEntry::make('total')->money('USD'),
])
->columns(3),
]),
// Quick actions
Section::make('Quick Actions')
->schema([
Actions::make([
Action::make('new_order')
->label('Create Order')
->url(OrderResource::getUrl('create')),
Action::make('export')
->label('Export Report')
->action(fn () => $this->export()),
]),
]),
]);
}
protected function statCard(string $label, string $value, string $icon): Component
{
return Section::make()
->schema([
IconEntry::make('icon')
->icon($icon)
->size('lg'),
TextEntry::make('value')
->state($value)
->size('xl')
->weight('bold'),
TextEntry::make('label')
->state($label)
->color('gray'),
])
->extraAttributes(['class' => 'text-center']);
}
}
Conditional Schemas
Show different components based on state:
public function schema(Schema $schema): Schema
{
return $schema->components([
Select::make('type')
->options(['individual' => 'Individual', 'company' => 'Company'])
->live(),
// Show for individuals
Section::make('Personal Information')
->visible(fn (Get $get) => $get('type') === 'individual')
->schema([
TextInput::make('first_name'),
TextInput::make('last_name'),
DatePicker::make('birth_date'),
]),
// Show for companies
Section::make('Company Information')
->visible(fn (Get $get) => $get('type') === 'company')
->schema([
TextInput::make('company_name'),
TextInput::make('registration_number'),
TextInput::make('vat_number'),
]),
]);
}
Reusable Schema Components
Create component factories:
// app/Filament/Schemas/AddressSchema.php
class AddressSchema
{
public static function make(string $prefix = ''): array
{
return [
TextInput::make($prefix . 'street')
->label('Street Address')
->required(),
Grid::make(3)->schema([
TextInput::make($prefix . 'city')->required(),
TextInput::make($prefix . 'state')->required(),
TextInput::make($prefix . 'zip')->required(),
]),
Select::make($prefix . 'country')
->options(Country::pluck('name', 'code'))
->searchable(),
];
}
}
// Usage
Section::make('Billing Address')
->schema(AddressSchema::make('billing_')),
Section::make('Shipping Address')
->schema(AddressSchema::make('shipping_')),
Schema Validation
Combine validation across component types:
public function schema(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('email')
->email()
->required()
->unique('users', 'email'),
TextInput::make('password')
->password()
->required()
->minLength(8)
->confirmed(),
TextInput::make('password_confirmation')
->password()
->required(),
])
->statePath('data');
}
public function save(): void
{
$data = $this->form->getState(); // Validates all fields
User::create($data);
}
Performance Optimization
Partial Rendering
TextInput::make('search')
->live()
->partiallyRenderComponentsAfterStateUpdated()
->afterStateUpdated(fn () => $this->search());
Deferred Loading
Section::make('Heavy Data')
->deferred()
->schema([
// Only loads when section is visible
RepeatableEntry::make('items')
->schema([...]),
]);
Conclusion
Schemas represent a paradigm shift in how we build admin interfaces. By unifying forms, infolists, and actions into a single system, FilamentPHP v4 lets you build complex UIs entirely in PHP—no JavaScript required.
The key insight is that server-driven UI doesn't mean sacrificing interactivity. With Livewire under the hood, schemas feel responsive while keeping all logic on the server.