Filament v4 Bulk Actions: Modals and Authorization | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Filament v4 Table Bulk Actions: Custom Confirmation Modals and Scoped Authorization

 Filament v4 Table Bulk Actions: Custom Confirmation Modals and Scoped Authorization
====================================================================================

 Go beyond the default delete bulk action. Learn how to build custom Filament v4 bulk actions with typed confirmation modals, per-record authorization checks, and clean job dispatch patterns.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 12 Sep 2026 · Updated 12 Sep 2026 · 4 min read

ShareCopy linkCopied

 ![Filament v4 Table Bulk Actions: Custom Confirmation Modals and Scoped Authorization](https://cdn.msaied.com/661/5f319b485f1bc0c76e2c82746f730c8c.png) 

  On this page +1. [Why Bulk Actions Deserve More Attention](#why-bulk-actions-deserve-more-attention)
2. [Defining a Custom Bulk Action](#defining-a-custom-bulk-action)
3. [Per-Record Authorization Inside the Action](#per-record-authorization-inside-the-action)
4. [Adding a Custom Confirmation Form](#adding-a-custom-confirmation-form)
5. [Chunking Large Selections Safely](#chunking-large-selections-safely)
6. [Key Takeaways](#key-takeaways)

 Why Bulk Actions Deserve More Attention
---------------------------------------

Most Filament tutorials show `DeleteBulkAction` and move on. In real applications, bulk actions are where business logic concentrates — mass approvals, batch exports, multi-record state transitions — and they need proper authorization, user feedback, and reliable async dispatch.

This article focuses on Filament v4's bulk action API, which ships with the unified Schema system and a cleaner modal builder.

---

Defining a Custom Bulk Action
-----------------------------

Register bulk actions inside `table()` on your resource's `ListRecords` page or directly in the `Table` definition:

```php
use Filament\Tables\Actions\BulkAction;
use Illuminate\Database\Eloquent\Collection;

BulkAction::make('approve')
    ->label('Approve Selected')
    ->icon('heroicon-o-check-circle')
    ->color('success')
    ->requiresConfirmation()
    ->modalHeading('Approve Applications')
    ->modalDescription('Only applications you are authorised to approve will be processed.')
    ->modalSubmitActionLabel('Approve Now')
    ->action(function (Collection $records): void {
        $records
            ->filter(fn ($r) => auth()->user()->can('approve', $r))
            ->each(fn ($r) => ApproveApplicationJob::dispatch($r));
    })
    ->deselectRecordsAfterCompletion()

```

The `requiresConfirmation()` call renders a modal automatically. The `modalDescription` string is the right place to set user expectations about partial authorization.

---

Per-Record Authorization Inside the Action
------------------------------------------

Filament's `authorize()` callback controls whether the bulk action *button* is visible, but it does not filter individual records. Always filter inside `action()` itself:

```php
->authorize(fn (): bool => auth()->user()->can('approve-applications'))
->action(function (Collection $records): void {
    $authorized = $records->filter(
        fn ($record) => auth()->user()->can('approve', $record)
    );

    if ($authorized->isEmpty()) {
        Notification::make()
            ->title('Nothing to approve')
            ->warning()
            ->send();
        return;
    }

    $authorized->each(fn ($r) => ApproveApplicationJob::dispatch($r));

    Notification::make()
        ->title("{$authorized->count()} application(s) queued for approval")
        ->success()
        ->send();
})

```

This pattern prevents privilege escalation when a user selects records they own alongside records they do not.

---

Adding a Custom Confirmation Form
---------------------------------

Sometimes you need extra input before the action runs — a reason field, a scheduled date, a confirmation string. Filament v4 lets you attach a Schema-based form to any bulk action modal:

```php
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\DatePicker;

BulkAction::make('schedule_review')
    ->label('Schedule Review')
    ->form([
        DatePicker::make('review_date')
            ->label('Review Date')
            ->required()
            ->minDate(today()->addDay()),
        Textarea::make('notes')
            ->label('Internal Notes')
            ->maxLength(500),
    ])
    ->action(function (Collection $records, array $data): void {
        $records->each(
            fn ($r) => $r->update([
                'review_date' => $data['review_date'],
                'review_notes' => $data['notes'],
                'status' => 'pending_review',
            ])
        );
    })
    ->deselectRecordsAfterCompletion()

```

The `$data` array is automatically validated against the form rules before `action()` fires.

---

Chunking Large Selections Safely
--------------------------------

When a user selects thousands of records, loading them all into a `Collection` will exhaust memory. Dispatch a single job that re-queries by IDs instead:

```php
->action(function (Collection $records): void {
    $ids = $records->modelKeys();

    ProcessBulkApprovalJob::dispatch($ids, auth()->id());

    Notification::make()
        ->title('Bulk approval queued')
        ->body(count($ids) . ' records will be processed in the background.')
        ->success()
        ->send();
})

```

Inside `ProcessBulkApprovalJob`, chunk the IDs:

```php
public function handle(): void
{
    $approver = User::findOrFail($this->approverId);

    collect($this->ids)->chunk(100)->each(function ($chunk) use ($approver) {
        Application::whereIn('id', $chunk)
            ->get()
            ->filter(fn ($r) => $approver->can('approve', $r))
            ->each(fn ($r) => $r->markApproved($approver));
    });
}

```

---

Key Takeaways
-------------

- Always filter records by policy inside `action()` — the `authorize()` callback only gates visibility.
- Use `->form([...])` on a `BulkAction` to collect structured input before execution.
- For large selections, pass only IDs to a queued job and re-query with chunking.
- `deselectRecordsAfterCompletion()` prevents stale checkbox state after async operations.
- Send contextual `Notification` feedback so users know when partial authorization reduced the scope.

- [filament](https://www.msaied.com/public/articles?search=filament)
- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [authorization](https://www.msaied.com/public/articles?search=authorization)
- [queues](https://www.msaied.com/public/articles?search=queues)

 Frequently asked questions 
---------------------------

  Does Filament's authorize() callback on a BulkAction filter individual records?No. The authorize() callback only controls whether the action button is rendered for the current user. You must filter individual records inside the action() closure using your policy or gate checks.

   How do I pass extra user input to a Filament bulk action?Attach a -&gt;form(\[...\]) array of Filament form components to the BulkAction. The validated data is injected as a $data array into the action() closure automatically.

   What is the safest way to handle bulk actions on thousands of records in Filament?Extract only the record IDs from the Collection using modelKeys(), then dispatch a single queued job with those IDs. Inside the job, re-query and chunk the IDs to avoid memory exhaustion.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleLaravel Horizon: Queue Metrics, Supervisor Tuning, and Safe Deployments](https://www.msaied.com/public/articles/laravel-horizon-queue-metrics-supervisor-tuning-and-safe-deployments) [Next articleModular Monolith in Laravel: Enforcing Bounded Contexts with Module Service Providers](https://www.msaied.com/public/articles/modular-monolith-in-laravel-enforcing-bounded-contexts-with-module-service-providers)  

   On this page
-------------

1. [Why Bulk Actions Deserve More Attention](#why-bulk-actions-deserve-more-attention)
2. [Defining a Custom Bulk Action](#defining-a-custom-bulk-action)
3. [Per-Record Authorization Inside the Action](#per-record-authorization-inside-the-action)
4. [Adding a Custom Confirmation Form](#adding-a-custom-confirmation-form)
5. [Chunking Large Selections Safely](#chunking-large-selections-safely)
6. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
