Filament Snippets for everyday use!
6 min readJul 11, 2024
I want to share some useful code snippets for Filament’s everyday use!
Optional methods in the Panel Configuration
return $panel
// Default Methods
->passwordReset() // Add native Password Reset function
->topNavigation() // Move Navbar from side to top
->brandName('Company inc') // Name of the Brand
->brandLogo('/images/logo.png')) // Application Logo
->brandLogoHeight('50px') // Height of the Logo
->favicon('/favicon.ico') // Application Favicon
->colors([ // Add Custom Application Colors
'primary' => \Filament\Support\Colors\Color::Slate,
])
->userMenuItems([ // Add Items to User Menu
\Filament\Navigation\MenuItem::make()
->label(__('User Menu Item A'))
->url('/url-item-a')
->icon('heroicon-o-item-a'),
\Filament\Navigation\MenuItem::make()
->label(__('User Menu Item B'))
->url('/url-item-b')
->icon('heroicon-o-item-b'),
\Filament\Navigation\MenuItem::make()
->label(__('User Menu Item C'))
->url('/url-item-c')
->icon('heroicon-o-item-c'),
]);
Optional settings in Resources
// Hide Resource in the Menu
protected static bool $shouldRegisterNavigation = false;
// Set a Navigation Sort
protected static ?int $navigationSort = 30;
// Set a Custom Icon
protected static ?string $navigationIcon = 'heroicon-o-custom-icon';
// Add a Badge in Navigation
public static function getNavigationBadge(): ?string
{
return Blog::where('is_pending', true)->count;
}
// Set a Custom Singular Label for the Resource
public static function getModelLabel(): string
{
return __('Post');
}
// or you can use: protected static ?string $modelLabel = 'Post';
// Set a Custom Plural Label for the Resource
public static function getPluralModelLabel(): string
{
return __('Posts');
}
// or you can use: protected static ?string $pluralModelLabel = 'Posts';
// Set who can view the Resource
public static function canViewAny(): bool
{
return Auth::user()->is_admin;
}
// Customize the query of the resource
public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder
{
return parent::getEloquentQuery()->where('is_approved', true);
}
// Global Search Attribute
protected static ?string $recordTitleAttribute = 'customer.name';
// In migration, you could add a virtual field with "concats" such as:
// $table->string('name')->virtualAs('concat(lastname, \' \', firstname)');
// Global Search Result Details
public static function getGlobalSearchResultDetails(\Illuminate\Database\Eloquent\Model $record): array
{
return [
__('Code') => '#'.$record->code,
__('Customer') => $record->customer->name,
];
}
// Customize Record Label
public static function getRecordLabel($record): string
{
return $record->name;
}
Add Confirmation on the native Save Action
// Use it in files such as this one: "app/Filament/Resources/ModelResource/Pages/EditModel.php"
protected function getSaveFormAction(): \Filament\Actions\Action
{
return \Filament\Actions\Action::make('save')
->requiresConfirmation(fn ($livewire) => (!$livewire->data['is_draft'])
->modalHeading(fn ($livewire) => (!$livewire->data['is_draft']) ? __('Confirm to publish') : false)
->modalDescription(
fn ($livewire) => (!$livewire->data['is_draft']) ? __('Are you sure to publish this Post?') : false
)
->action(fn ($livewire) => $livewire->save())
->keyBindings(['mod+s']);
}
Make a custom action
->actions([
Action::make('approve-action')
->visible(fn ($record) => ! $record->is_approved)
->color('warning')
->label('Action Lable')
->icon('heroicon-o-custom-action-icon')
->requiresConfirmation()
->modalIcon('heroicon-o-custom-confirm-icon')
->modalHeading('Confirm Action')
->modalDescription('Are you sure?')
->modalSubmitActionLabel('Confirm')
->action(function ($record) {
$entity = Entity::find($record->id);
$entity->is_approved = true;
$entity->save();
// Send notification in panel
return \Filament\Notifications\Notification::make()
->title(fn($entity) => 'Record '.$entity->name.' has been approved!')
->success()
->send();
}
),
// other actions...
Some Examples of Form Components
// Related Select
Forms\Components\Select::make('category_id')
->label('Category')
->relationship('category', 'category_name')
->searchable()
->preload() // preload record without search
->columnSpanFull(),
// Related Select with Custom Query and Lable
\Filament\Forms\Components\Select::make('category_id')
->label('Category')
->relationship(
name: 'category',
modifyQueryUsing: function ($query) {
return $query->where('is_online', true)->orderBy('name', 'asc');
}
)
->getOptionLabelFromRecordUsing(
fn ($record) => $record->name.' with '. count($record->items). ' elements')
)
->columnSpanFull()
->live()
->reactive()
->afterStateUpdated(fn ($set) => $set('subcategory_id', null))
->required(),
// Related Select with Custom Query (2th method) and State Update
\Filament\Forms\Components\Select::make('category_id')
->label('Category')
->options(function () {
return \App\Models\Customer::where('is_online', true)->orderBy('name')->get()->pluck('name', 'id');
})
->required()
->live()
->afterStateUpdated(function ($get, $set) {
$set('subcategory_id', null);
})
->searchable(),
// Amount field
Forms\Components\TextInput::make('amount')
->default(0)
->minValue(0)
->numeric()
->prefix('€')
->required(),
// Image field
\Filament\Forms\Components\FileUpload::make('image')
->label('Photo')
->image()
->imageEditor()
->maxSize(512),
// Date Range with Min and Max limits
\Filament\Forms\Components\Section::make()
->description('Date Range')
->columns(2)
->schema([
\Filament\Forms\Components\DatePicker::make('from')
->label(__('From'))
->maxDate(fn (\Filament\Forms\Get $get) => $get('to'))
->required(),
\Filament\Forms\Components\DatePicker::make('to')
->label(__('To'))
->minDate(fn (\Filament\Forms\Get $get) => $get('from'))
->maxDate(now()->addYear(5))
->required(),
]
),
// Dependent selects and fields (dummy example)
\Filament\Forms\Components\Section::make()
->columns(3)
->schema([
\Filament\Forms\Components\TextInput::make('address')
->label(__('Address'))
->columnSpanFull()
->required(),
\Filament\Forms\Components\TextInput::make('city')
->label(__('City'))
->visible(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address')),
\Filament\Forms\Components\TextInput::make('zipcode')
->label(__('Zip Code'))
->visible(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address')),
\Filament\Forms\Components\TextInput::make('province')
->label(__('Country'))
->visible(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address')),
\Filament\Forms\Components\Select::make('province')
->label(__('Province'))
->afterStateUpdated(function ($get, $set) {
$set('city', null);
$set('zipcode', null);
})
->options(function () {
return \App\Models\Province::orderBy('name')->get()->pluck('name', 'id');
})
->live()
->visible(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address')),
\Filament\Forms\Components\Select::make('city')
->label(__('City'))
->options(function ($get, $set) {
if (! $get('province')) {
$set('city', null);
return [];
}
return \App\Models\City::where('province_id', $get('province'))->pluck('name', 'id');
})
->visible(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->reactive()
->live(),
\Filament\Forms\Components\Select::make('zipcode')
->label(__('Zip Code'))
->options(function ($get, $set) {
if (! $get('city')) {
$set('zipcode', null);
return [];
}
return \App\Models\Zip::where('city_id', $get('city'))->pluck('code', 'code');
})
->visible(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->preload()
->reactive()
->live(),
\Filament\Forms\Components\Checkbox::make('not_italian_address')
->label(__('Not Italian address'))
->columnSpanFull()
->afterStateUpdated(function ($set) {
$set('city', null);
$set('zipcode', null);
$set('province', null);
})
->live()
->formatStateUsing(function ($get) {
return ! is_null($get('city')) && ! is_numeric($get('city'));
}),
]),
// Simple Users form handle (with optional password change)
return $form
->schema([
\Filament\Forms\Components\TextInput::make('name')
->required(),
\Filament\Forms\Components\TextInput::make('email')
->email()
->required(),
\Filament\Forms\Components\TextInput::make('password')
->password()
->rules([\App\Settings\Password::validation()])
->dehydrateStateUsing(fn ($state) => \Illuminate\Support\Facades\Hash::make($state))
->dehydrated(fn ($state) => filled($state))
->required(fn (string $context): bool => $context === 'create'),
]);
// EU Vat ID Validation
Forms\Components\TextInput::make('vat_id')
->columnSpanFull()
->helperText('e.g. IT1234567890')
->required(fn (Get $get): bool => $get('is_company'))
->visible(fn (Get $get): bool => $get('is_company'))
->alphaNum()
->requiredWithout('tax_id')
->regex('/^((AT)?U[0-9]{8}|(BE)?0[0-9]{9}|(BG)?[0-9]{9,10}|(CY)?[0-9]{8}L|(CZ)?[0-9]{8,10}|(DE)?[0-9]{9}|(DK)?[0-9]{8}|(EE)?[0-9]{9}|
(EL|GR)?[0-9]{9}|(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]|(FI)?[0-9]{8}|(FR)?[0-9A-Z]{2}[0-9]{9}|(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})|(HU)?[0-9]{8}|(IE)?[0-9]S[0-9]{5}L|(IT)?[0-9]{11}|(LT)?([0-9]{9}|[0-9]{12})|(LU)?[0-9]{8}|(LV)?[0-9]{11}|(MT)?[0-9]{8}|(NL)?[0-9]{9}B[0-9]{2}|(PL)?[0-9]{10}|(PT)?[0-9]{9}|(RO)?[0-9]{2,10}|(SE)?[0-9]{12}|(SI)?[0-9]{8}|(SK)?[0-9]{10})$/i')
->unique('customers', 'vat_id', fn ($record) => $record)
->live(),
Some Examples of Table Components
// HTML in columns
\Filament\Tables\Columns\TextColumn::make('col_field')
->label(__('Test Col'))
->sortable()
->formatStateUsing(fn ($record) => '<b>'.$record->col_field.'</b>')
->html()
->sortable()
->searchable(),
// Related Select with Custom Query and Lable
\Filament\Forms\Components\Select::make('category_id')
->label('Category')
->relationship(
name: 'category',
modifyQueryUsing: function ($query) {
return $query->where('is_online', true)->orderBy('name', 'asc');
}
)
->getOptionLabelFromRecordUsing(
fn ($record) => $record->name.' with '. count($record->items). ' elements')
)
->columnSpanFull()
->live()
->reactive()
->afterStateUpdated(fn ($set) => $set('subcategory_id', null))
->required(),
// Related Select with Custom Query (2th method) and State Update
\Filament\Forms\Components\Select::make('category_id')
->label('Category')
->options(function () {
return \App\Models\Customer::where('is_online', true)->orderBy('name')->get()->pluck('name', 'id');
})
->required()
->live()
->afterStateUpdated(function ($get, $set) {
$set('subcategory_id', null);
})
->searchable(),
// Amount field
Forms\Components\TextInput::make('amount')
->default(0)
->minValue(0)
->numeric()
->prefix('€')
->required(),
// Image field
\Filament\Forms\Components\FileUpload::make('image')
->label('Photo')
->image()
->imageEditor()
->maxSize(512),
// Date Range with Min and Max limits
\Filament\Forms\Components\Section::make()
->description('Date Range')
->columns(2)
->schema([
\Filament\Forms\Components\DatePicker::make('from')
->label(__('From'))
->maxDate(fn (\Filament\Forms\Get $get) => $get('to'))
->required(),
\Filament\Forms\Components\DatePicker::make('to')
->label(__('To'))
->minDate(fn (\Filament\Forms\Get $get) => $get('from'))
->maxDate(now()->addYear(5))
->required(),
]
),
// Dependent selects and fields (dummy example)
\Filament\Forms\Components\Section::make()
->columns(3)
->schema([
\Filament\Forms\Components\TextInput::make('address')
->label(__('Address'))
->columnSpanFull()
->required(),
\Filament\Forms\Components\TextInput::make('city')
->label(__('City'))
->visible(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address')),
\Filament\Forms\Components\TextInput::make('zipcode')
->label(__('Zip Code'))
->visible(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address')),
\Filament\Forms\Components\TextInput::make('province')
->label(__('Country'))
->visible(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => $get('not_italian_address')),
\Filament\Forms\Components\Select::make('province')
->label(__('Province'))
->afterStateUpdated(function ($get, $set) {
$set('city', null);
$set('zipcode', null);
})
->options(function () {
return \App\Models\Province::orderBy('name')->get()->pluck('name', 'id');
})
->live()
->visible(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address')),
\Filament\Forms\Components\Select::make('city')
->label(__('City'))
->options(function ($get, $set) {
if (! $get('province')) {
$set('city', null);
return [];
}
return \App\Models\City::where('province_id', $get('province'))->pluck('name', 'id');
})
->visible(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->reactive()
->live(),
\Filament\Forms\Components\Select::make('zipcode')
->label(__('Zip Code'))
->options(function ($get, $set) {
if (! $get('city')) {
$set('zipcode', null);
return [];
}
return \App\Models\Zip::where('city_id', $get('city'))->pluck('code', 'code');
})
->visible(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->required(fn (\Filament\Forms\Get $get): bool => ! $get('not_italian_address'))
->preload()
->reactive()
->live(),
\Filament\Forms\Components\Checkbox::make('not_italian_address')
->label(__('Not Italian address'))
->columnSpanFull()
->afterStateUpdated(function ($set) {
$set('city', null);
$set('zipcode', null);
$set('province', null);
})
->live()
->formatStateUsing(function ($get) {
return ! is_null($get('city')) && ! is_numeric($get('city'));
}),
]),
// Simple Users form handle (with optional password change)
return $form
->schema([ \Filament\Forms\Components\TextInput::make('name') ->required(), \Filament\Forms\Components\TextInput::make('email') ->email() ->required(), \Filament\Forms\Components\TextInput::make('password') ->password() ->rules([\App\Settings\Password::validation()])
->dehydrateStateUsing(fn ($state) => \Illuminate\Support\Facades\Hash::make($state))
->dehydrated(fn ($state) => filled($state))
->required(fn (string $context): bool => $context === 'create'),
]);
// EU Vat ID Validation
Forms\Components\TextInput::make('vat_id')
->columnSpanFull()
->helperText('e.g. IT1234567890')
->required(fn (Get $get): bool => $get('is_company'))
->visible(fn (Get $get): bool => $get('is_company'))
->alphaNum()
->requiredWithout('tax_id')
->regex('/^((AT)?U[0-9]{8}|(BE)?0[0-9]{9}|(BG)?[0-9]{9,10}|(CY)?[0-9]{8}L|(CZ)?[0-9]{8,10}|(DE)?[0-9]{9}|(DK)?[0-9]{8}|(EE)?[0-9]{9}|
(EL|GR)?[0-9]{9}|(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]|(FI)?[0-9]{8}|(FR)?[0-9A-Z]{2}[0-9]{9}|(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})|(HU)?[0-9]{8}|(IE)?[0-9]S[0-9]{5}L|(IT)?[0-9]{11}|(LT)?([0-9]{9}|[0-9]{12})|(LU)?[0-9]{8}|(LV)?[0-9]{11}|(MT)?[0-9]{8}|(NL)?[0-9]{9}B[0-9]{2}|(PL)?[0-9]{10}|(PT)?[0-9]{9}|(RO)?[0-9]{2,10}|(SE)?[0-9]{12}|(SI)?[0-9]{8}|(SK)?[0-9]{10})$/i')
->unique('customers', 'vat_id', fn ($record) => $record)
->live(),
Options in Resource Relation Page
// Make Relation readonly if condition
public function isReadOnly(): bool
{
return !Auth::user()->is_admin;
}
// Hide Relation if condition
public static function canViewForRecord(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): bool
{
return !Auth::user()->is_admin;
}
// Set a custom label for the relation
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
{
return 'Title';
}
// Set Relation OwnRecord to SubResource via $livewire->ownerRecord
Forms\Components\Select::make('customer_id')
->label('Customer')
->relationship('customer', 'name')
->default(fn ($livewire) => (isset($livewire->ownerRecord)) ? $livewire->ownerRecord->id : null),
These tips will be helpful to the community!