Mastering Filament Wizards: Advanced Techniques and Best Practices
Ali Mousavi
Senior Backend Developer | PHP, Laravel | 10+ Years Experience | Proficient in Vue.js & Nuxt.js
Wizards in Filament provide a powerful way to break down complex forms into manageable steps. Let's dive deep into advanced techniques and patterns for creating sophisticated wizard interfaces.
Basic Wizard Setup with Advanced Features
use Filament\Forms\Components\Wizard;
use Filament\Forms\Components\Wizard\Step;
public static function form(Form $form): Form
{
return $form
->schema([
Wizard::make([
Step::make('Basic Information')
->icon('heroicon-o-user')
->schema([
TextInput::make('title')
->required()
->maxLength(255),
Select::make('category_id')
->relationship('category', 'name')
->required(),
])
->description('Fill in the basic details'),
Step::make('Content')
->icon('heroicon-o-document-text')
->schema([
RichEditor::make('content')
->required(),
]),
Step::make('Settings')
->icon('heroicon-o-cog')
->schema([
Toggle::make('is_published'),
DateTimePicker::make('published_at'),
]),
])
->skippable() // Allow skipping steps
->startOnStep(2) // Start on specific step
->submitAction('Create Post') // Custom submit button text
->persistStepInQueryString('step') // Persist step in URL
]);
}
Conditional Steps and Dynamic Logic
Wizard::make([
Step::make('Account Type')
->schema([
Select::make('account_type')
->options([
'personal' => 'Personal',
'business' => 'Business',
])
->reactive()
->required(),
]),
Step::make('Business Details')
->schema([
TextInput::make('company_name'),
TextInput::make('vat_number'),
])
->visible(fn (callable $get) => $get('account_type') === 'business'),
Step::make('Personal Details')
->schema([
TextInput::make('first_name'),
TextInput::make('last_name'),
])
->visible(fn (callable $get) => $get('account_type') === 'personal'),
])
Advanced Validation and Step Dependencies
class CreateUserWizard extends Component implements HasForms
{
public $state = [
'account' => [],
'profile' => [],
'preferences' => [],
];
protected function getFormSchema(): array
{
return [
Wizard::make([
Step::make('Account')
->schema([
TextInput::make('account.email')
->email()
->required()
->unique('users', 'email'),
TextInput::make('account.password')
->password()
->required()
->minLength(8),
])
->beforeValidation(function () {
// Custom validation logic
}),
Step::make('Profile')
->schema([
FileUpload::make('profile.avatar')
->image()
->maxSize(1024),
])
->afterValidation(function () {
// Post-validation processing
}),
Step::make('Preferences')
->schema([
CheckboxList::make('preferences.notifications')
->options([
'email' => 'Email',
'sms' => 'SMS',
]),
]),
])
->enableStepIndex(false) // Hide step index
->disabled(fn () => $this->isProcessing)
];
}
}
Custom Step Navigation and Actions
Wizard::make([
// Steps configuration
])
->beforeStep(function ($step, $previousStep) {
// Logic before moving to next step
if ($previousStep === 'payment' && !$this->paymentVerified) {
$this->addError('payment', 'Payment verification failed');
return false;
}
return true;
})
->afterStep(function ($step) {
// Logic after completing a step
if ($step === 'document_upload') {
ProcessUploadedDocuments::dispatch($this->state['documents']);
}
})
->submitAction(
Action::make('submit')
->label('Complete Registration')
->action(function (array $data) {
// Custom submission logic
})
)
Wizard with Progress Tracking
class RegistrationWizard extends Component implements HasForms
{
public $progress = 0;
protected function getFormSchema(): array
{
return [
Wizard::make([
// Steps configuration
])
->afterStep(function ($step) {
$totalSteps = count($this->getSteps());
$currentStep = array_search($step, array_keys($this->getSteps())) + 1;
$this->progress = ($currentStep / $totalSteps) * 100;
})
];
}
public function render()
{
return view('registration-wizard', [
'progress' => $this->progress
]);
}
}
State Management and Data Persistence
class ComplexWizard extends Component implements HasForms
{
public $state = [];
protected function getFormSchema(): array
{
return [
Wizard::make([
Step::make('Step 1')
->schema([
// Fields
])
->beforeValidation(function () {
$this->state['timestamp'] = now();
})
->afterValidation(function () {
Cache::put(
"wizard.{$this->userId}.step1",
$this->state,
now()->addHour()
);
}),
])
->startOnStep(
Cache::get("wizard.{$this->userId}.lastStep", 1)
)
];
}
}
Integration with External Services
Wizard::make([
Step::make('Payment Information')
->schema([
Select::make('payment_method')
->options([
'credit_card' => 'Credit Card',
'paypal' => 'PayPal',
])
->reactive(),
Group::make()
->schema([
TextInput::make('card_number'),
TextInput::make('expiry'),
TextInput::make('cvv'),
])
->visible(fn (callable $get) =>
$get('payment_method') === 'credit_card'
),
])
->afterValidation(function (array $state) {
// Integrate with payment gateway
$response = PaymentGateway::process($state);
if (!$response->successful()) {
$this->addError(
'payment',
'Payment processing failed: ' . $response->message
);
return false;
}
}),
])
Best Practices and Tips
1. Break Down Complex Forms:
protected function getSteps(): array
{
return [
'basic' => $this->getBasicInformationStep(),
'details' => $this->getDetailedInformationStep(),
'review' => $this->getReviewStep(),
];
}
protected function getBasicInformationStep(): Step
{
return Step::make('Basic Information')
->schema([
// Fields
]);
}
2. Handle Errors Gracefully:
->afterStep(function ($step) {
try {
// Process step
} catch (Exception $e) {
Log::error('Wizard step failed', [
'step' => $step,
'error' => $e->getMessage(),
]);
$this->addError('wizard', 'An error occurred');
return false;
}
})
3. Implement Save Draft Functionality:
public function saveDraft()
{
$data = $this->form->getState();
Draft::create([
'user_id' => auth()->id(),
'data' => $data,
'step' => $this->currentStep,
]);
Notification::make()
->title('Draft saved successfully')
->success()
->send();
}
These advanced techniques will help you create sophisticated wizard interfaces that handle complex workflows while maintaining a great user experience. Remember to consider validation, state management, and error handling when implementing these features.