Courses

Filament 3 From Scratch: Practical Course

Table Actions: Row / Bulk / Header

Summary of this lesson:
- Creating custom table actions
- Implementing bulk actions
- Adding header actions
- Managing action groups

In the process of while we're building simple tables, you got already familiar with three types of actions:

  • Edit/Delete buttons in a column
  • Create a button on top
  • Multi-checkbox Bulk Actions on top-left

In this lesson, we will look at how to create custom actions for all those cases and more.


Edit/Delete + Extra Buttons

Let's try to add another button to the Orders table, like "Mark as Complete," which would require confirmation and would change the status of the order.

Migration:

Schema::table('orders', function (Blueprint $table) {
$table->boolean('is_completed')->default(false);
});

Also, we make it fillable:

app/Models/Order.php:

class Order extends Model
{
use HasFactory;
 
protected $fillable = [
'user_id',
'product_id',
'price',
'is_completed',
];

Now, here's what action we can add to the table:

app/Filament/Resources/OrderResource.php:

return $table
->columns([
// ...
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('Mark Completed')
->requiresConfirmation()
->icon('heroicon-o-check-badge')
->hidden(fn (Order $record) => $record->is_completed)
->action(fn (Order $record) => $record->update(['is_completed' => true])),
])

Here's the visual result:

Pretty sure that the code above is self-explanatory, but here are a few things to notice:

  • make('Mark Completed') parameter means the label of the link
  • requiresConfirmation() will show the confirmation modal, like in the Delete action
  • icon can be chosen from the Blade Icons package
  • methods hidden() and action() both accept callback functions
  • hidden() should return true/false with the condition when the link should be hidden. In the screenshot above, you see one record as already completed

If you want to save some space in a table row, you may hide the actions under a vertical dots icon with ActionGroup():

return $table
->columns([
// ...
])
->actions([
Tables\Actions\ActionGroup::make([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('Mark Completed')
->requiresConfirmation()
->hidden(fn (Order $record) => $record->is_completed)
->icon('heroicon-o-check-badge')
->action(fn (Order $record) => $record->update(['is_completed' => true])),
])
])

This is how it looks now.

So this is how you add any custom action to your table "Actions column".


Table Action with Modal Edit Form

What if you want to have some custom mini-edit form: you click the link in the table, there's a modal form with a few fields (but not full edit), and you submit?

Let's implement it with "Mark as completed" but as a modal form with a checkbox. Not a very practical example, I know, but the goal is for you to understand the idea of how it works.

->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('Change is completed')
->icon('heroicon-o-check-badge')
->fillForm(function (Order $order) {
return ['is_completed' => $order->is_completed];
})
->form([
Forms\Components\Checkbox::make('is_completed'),
])
->action(function (Order $order, array $data): void {
$order->update(['is_completed' => $data['is_completed']]);
}),

Here's the visual result:

As you can see, you need to fill in three things:

  • form fields
  • fill that form (it's not filled in automatically)
  • action to update the record after submit

Bulk Action: Add More to "Delete Selected"

By default, Filament shows a checkbox for each table record and allows to bulk-process them somehow. The only default function is "Bulk Delete", but you can add your own.

In the same table() method, there's a method bulkActions() where you pass the array of the Actions you want.

Let's try to implement "Bulk Mark as Completed".

Here's the code:

use Illuminate\Database\Eloquent\Collection;
 
// ...
 
return $table
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\BulkAction::make('Mark as Completed')
->icon('heroicon-o-check-badge')
->requiresConfirmation()
->action(fn (Collection $records) => $records->each->update(['is_completed' => true]))
->deselectRecordsAfterCompletion()
]),
])

Here's how it looks:

Instead of Action::make(), we have a BulkAction::make(), but other options are almost identical.

The difference in the action() method is that we work with Eloquent Collection and not one specific record.

Also, the method deselectRecordsAfterCompletion() makes sure that the selected checkboxes are de-selected after the action is processed.


Header Actions for All Table

There's also one more place where you can place Actions: on top of the table. It's called "Header Actions".

A pretty typical example would be to exporting records to Excel/CSV or if you just want to make other bulk actions more visible instead of hiding them under Bulk Actions.

Also, you can put a "Create new record" button there to make it more visible than the default top-right corner.

->headerActions([
Tables\Actions\Action::make('New Order')
->url(fn (): string => OrderResource::getUrl('create')),
])

Here's the button on top now:

Previous: Table Grouping and Summarizers
avatar

So, How to HIDE the default "New order" button on the top-right corner?

avatar

I get it!Comment out the line of method "getHeaderActions" in file ListOrders.php.

avatar

Yes, I cover that in the later lesson :)

avatar
You can use Markdown
avatar

Hi Povilas,

I get the following error when I perform the bulk action: App\Filament\Resources\OrderResource::App\Filament\Resources{closure}(): Argument #1 ($records) must be of type App\Filament\Resources\Collection, Illuminate\Database\Eloquent\Collection given, called in /Users/tobias/Sites/filament-

even when I manually reference: App\Filament\Resources\Collection,

what could it cause ?

avatar

Without code can't help

avatar

Actually you can help without seeing the code, because the clues are right there in the exception message ;)

Look at what it says: a Collection of type Illuminate\Database\Eloquent\Collection has been passed to the closure, but the closure's first argument is required to be a Collection of type App\Filament\Resources\Collection.

So the most likely explanation is that you've defined the closure parameter as Collection $record and in your uses clause at the top of your resource, Collection is defined as use App\Filament\Resources\Collection;.

Therefore, either change this to use Illuminate\Database\Eloquent\Collection or specify the fully-qualified name in the closure parameter definition: Illuminate\Database\Eloquent\Collection $record.

If you choose to change the use clause, you may find that the other Collection type is used elsewhere in your resource. In that case, you can alias the Eloquent collection by: use App\Filament\Resources\Collection as EloquentCollection; then use EloquentCollection $record as the closure parameter.

avatar

I'm sure there's a perfectly good explanation for this, but if the error just said (paraphrasing) that it had been given App\Filament\Resources\Collection but that it really wanted Illuminate\Database\Eloquent\Collection then it'd be a lot easier to debug.

Perhaps it's just me, but the way that error reads, it seems like it's received Illuminate\Database\Eloquent\Collection but wants App\Filament\Resources\Collection

Putting aside for the moment that there's actually no such thing as App\Filament\Resources\Collection it is still quite confusing.

But as I said above, I suspect there's a very sensible explanation as to why it has to be like this. So any additional pointers you can provide about how to properly read and understand this type of error message in the future would be really useful. Perhaps there's a key point or concept that one needs to grasp first to make it obvious?

avatar
You can use Markdown
avatar

Can I make a model action load PayPal or Braintree’s (that’s all that is supported where I live) JS API to take a payment.

Or to simplify the request - can the modal load some JS which returns a response that performs the action and closes the modal?

avatar
You can use Markdown
avatar

I suppose this would be as good a place as any to ask this question: When following these instructions and making the "Mark Completed" button, why is there such a long delay between pressing the button and the confirmation dialogue appearing?

From pressing the button to seeing the confirmation dialog, I'm waiting about 1-2 seconds. Yet when the confirmation dialogue finally appears, debugbar shows just 120ms.

avatar

Debugbar actually IS the reason of slowness.

Watch this video for more details.

avatar

Ahhh.... yes, that's funny. And, also, makes perfect sense :-)

avatar
You can use Markdown
avatar
You can use Markdown