Free·

Table Toggle Column with Custom Action

How to add custom business logic and validation to Filament's ToggleColumn using custom actions and/or confirmation modals.

The Problem

By default, Filament's ToggleColumn immediately updates the database when clicked, without any confirmation or ability to add custom validation logic. This becomes problematic when you need to:

  • Show a confirmation modal before making the change
  • Validate business rules before allowing the toggle
  • Prevent certain records from being toggled based on specific conditions
  • Provide user feedback when an action cannot be completed

For the sake of the example, let's prevent staff from changing chicken items and require confirmation before changing availability status of any product.

The Solution

The solution involves customizing the behaviour of the ToggleColumn updating process and then invoking a custom action - which must be hidden from the user interface with a class.

1. We create a dedicated file for this whole mechanism

This class defines three key methods:

  • action(): Creates the Filament action that we will place on the desired page.
  • updateStateUsing(): Replace the ToggleColumn saving state logic to invoke our custom action.
  • handleAction(): Contains the custom business logic and validation.
app/Filament/Resources/MenuItems/Actions/IsAvailable.php
<?php

namespace App\Filament\Resources\MenuItems\Actions;

use App\Models\MenuItem;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\Page;

class IsAvailable
{
    protected const string Action_ID = 'is_available_toggle';

    public static function action(): Action
    {
        return Action::make(static::Action_ID)
            ->action(static::handleAction(...))
            // You may have a custom form schema if needed, and get that in the arguments' injection
            // ->schema([
            //     TextInput...
            // ])
            ->requiresConfirmation()
            ->modalHeading(fn($arguments) => 'Change Availability of: ' . data_get($arguments, 'record.name') . '?')
            ->extraAttributes(['class' => 'hidden']);
    }

    public static function updateStateUsing(Page $livewire, MenuItem $record, $state): void
    {
        $livewire->mountAction(static::Action_ID, [
            'record' => $record,
            'state' => $state,
        ]);
    }

    public static function handleAction(Page $livewire, Action $action, array $arguments): void
    {
        $state = data_get($arguments, 'state');

        /** @var MenuItem $record */
        $record = $arguments['record'];

        // Prevent changing availability of chicken items, as an example business rule
        if (str($record->name)->contains(['Chicken', 'chicken'])) {
            Notification::make()
                ->title('You cannot change availability of chicken items.')
                ->danger()
                ->send();

            $livewire->unmountAction();

            $action->halt();
        }

        $record->is_available = $state;
        $record->save();
    }
}

2. Register the Action on the page

Add the action to the page's header actions - it will be hidden but accessible programmatically:

app/Filament/Resources/MenuItems/Pages/ListMenuItems.php
<?php

namespace App\Filament\Resources\MenuItems\Pages;

use App\Filament\Resources\MenuItems\Actions\IsAvailable;
use Filament\Actions\CreateAction;
use App\Filament\Resources\MenuItems\MenuItemResource;
use Filament\Resources\Pages\ListRecords;

class ListMenuItems extends ListRecords
{
    protected static string $resource = MenuItemResource::class;

    protected function getHeaderActions(): array
    {
        return [
            IsAvailable::action(),
            CreateAction::make(),
        ];
    }
}

3. Update the Table Column

Use ToggleColumn that uses the custom action to handle state updates:

app/Filament/Resources/MenuItems/Tables/MenuItemTable.php
<?php

namespace App\Filament\Resources\MenuItems\Tables;

use App\Filament\Resources\MenuItems\Actions\IsAvailable;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Table;

class MenuItemTable
{
    public static function configure(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('name')
                    ->searchable(),
                TextColumn::make('price')
                    ->money('USD')
                    ->sortable(),
                ToggleColumn::make('is_available')
                    ->label('Is available?')
                    ->updateStateUsing(IsAvailable::updateStateUsing(...)),
                TextColumn::make('created_at')
                    ->dateTime()
                    ->sortable()
                    ->toggleable(isToggledHiddenByDefault: true),
            ])
            ->filters([
                //
            ])
            ->recordActions([
                EditAction::make(),
            ])
            ->toolbarActions([
                BulkActionGroup::make([
                    DeleteBulkAction::make(),
                ]),
            ]);
    }
}

Now when users click the toggle, the custom action gets invoked, and we can customize however we see fit!