Custom Action di Filament: Lebih dari Tombol Hapus

Kalau baru mulai pakai Filament, gampang terpaku pada resource generator: bikin model, jalankan generator, jadi CRUD lengkap dalam hitungan menit. Tapi begitu aplikasi dipakai orang beneran, terutama di sistem internal semacam manajemen klinik atau approval dokumen, kebutuhan riilnya jarang sesederhana create-read-update-delete. Yang sering diminta justru aksi bisnis: "approve permintaan ini", "export laporan bulan ini ke Excel", "kirim notifikasi ke tim terkait". Di sinilah Action di Filament jadi salah satu fitur yang paling sering saya pakai, jauh lebih sering daripada yang saya kira waktu pertama belajar framework ini.

Action pada dasarnya adalah tombol dengan otak. Ia bisa punya ikon, warna, dialog konfirmasi, form input tambahan, validasi, sampai notifikasi hasil — semuanya tanpa perlu bikin halaman baru atau controller terpisah. Buat developer yang biasa nulis semuanya manual di Blade atau Vue, ini terasa seperti jalan pintas yang tetap rapi secara arsitektur.

Aksi sederhana: approve dengan konfirmasi

Contoh paling umum adalah aksi approve pada tabel resource. Daripada bikin route dan controller terpisah untuk satu tombol, saya biasa taruh langsung di table() resource-nya:

use Filament\Tables\Actions\Action;
use Filament\Notifications\Notification;

Action::make('approve')
    ->label('Setujui')
    ->icon('heroicon-o-check-circle')
    ->color('success')
    ->requiresConfirmation()
    ->modalHeading('Setujui permintaan ini?')
    ->modalDescription('Tindakan ini akan mengubah status dan tidak bisa dibatalkan.')
    ->visible(fn ($record) => $record->status === 'pending')
    ->action(function ($record) {
        $record->update(['status' => 'approved']);

        Notification::make()
            ->title('Permintaan disetujui')
            ->success()
            ->send();
    });

Tiga hal yang menurut saya wajib ada di aksi seperti ini: requiresConfirmation() supaya orang tidak asal klik, visible() supaya tombol hanya muncul kalau memang relevan dengan state data saat itu, dan notifikasi setelah aksi selesai supaya user tahu tindakannya berhasil. Tanpa tiga hal ini, aksi kustom justru menambah risiko human error, bukan mengurangi.

Validasi sebelum aksi dijalankan

Kadang aksi butuh input tambahan dari user, misalnya alasan penolakan, atau rentang tanggal untuk export. Filament memungkinkan Action punya form sendiri, lengkap dengan validasi, sebelum benar-benar dieksekusi:

use Filament\Forms\Components\Textarea;

Action::make('reject')
    ->label('Tolak')
    ->color('danger')
    ->form([
        Textarea::make('alasan')
            ->label('Alasan penolakan')
            ->required()
            ->minLength(10),
    ])
    ->action(function ($record, array $data) {
        $record->update([
            'status' => 'rejected',
            'rejection_reason' => $data['alasan'],
        ]);
    });

Poin penting di sini: validasi form action berjalan di layer yang sama dengan validasi form Laravel biasa, jadi rule seperti required, min, atau custom rule tetap berlaku normal. Saya cenderung selalu menambahkan validasi minimal di setiap aksi yang mengubah data, bahkan untuk hal yang kelihatannya sepele seperti alasan penolakan — karena di lapangan, kolom bebas tanpa validasi hampir selalu berakhir diisi asal-asalan atau malah kosong kalau frontend tidak memaksanya.

Aksi massal dan proses yang lebih berat

Untuk kebutuhan seperti export laporan atau kirim notifikasi ke banyak baris sekaligus, Filament punya BulkAction yang bekerja di atas Collection dari record yang dipilih user:

use Filament\Tables\Actions\BulkAction;

BulkAction::make('export')
    ->label('Export Terpilih')
    ->icon('heroicon-o-arrow-down-tray')
    ->action(function ($records) {
        // proses generate file, misalnya lewat Laravel Excel
        return response()->streamDownload(
            fn () => print(GenerateExport::forRecords($records)),
            'laporan.csv'
        );
    });

Untuk proses yang berat — misalnya export ribuan baris atau kirim notifikasi ke banyak penerima — saya menghindari menjalankannya secara sinkron di dalam action(). Lebih aman melempar pekerjaan itu ke queue job dan biarkan Action hanya bertugas men-dispatch, lalu kasih notifikasi "proses berjalan di latar belakang". Ini mencegah request HTTP timeout dan membuat UI tetap responsif, apalagi kalau aplikasinya dipakai bersamaan oleh banyak user di jam sibuk.

Satu prinsip umum yang saya pegang: Action sebaiknya tetap tipis. Logika bisnis inti — aturan approve, format export, isi notifikasi — idealnya hidup di service class atau model, bukan ditulis panjang lebar di closure Action. Action cukup jadi lapisan tipis yang menghubungkan UI dengan logika tersebut, plus urusan konfirmasi dan feedback ke user. Ini memudahkan testing juga, karena logikanya bisa diuji terpisah dari lapisan UI Filament.

Takeaway

Custom Action adalah salah satu alasan kenapa Filament terasa cocok untuk aplikasi internal yang workflow-nya lebih kompleks dari sekadar CRUD. Tiga hal yang selalu saya cek sebelum ship aksi baru: apakah butuh konfirmasi, apakah inputnya tervalidasi, dan apakah prosesnya cukup ringan untuk dijalankan langsung atau sebaiknya lewat queue. Kalau tiga hal itu sudah dipikirkan, sisanya biasanya tinggal soal styling ikon dan warna tombol saja.