Skip to content

fix: respect custom selectability in bulk actions when selecting all#20051

Open
hamdyelbatal122 wants to merge 3 commits into
filamentphp:4.xfrom
hamdyelbatal122:fix/select-all-non-selectable-records
Open

fix: respect custom selectability in bulk actions when selecting all#20051
hamdyelbatal122 wants to merge 3 commits into
filamentphp:4.xfrom
hamdyelbatal122:fix/select-all-non-selectable-records

Conversation

@hamdyelbatal122

Copy link
Copy Markdown
Contributor

Fixes #20050

This PR addresses the issue where bulk actions could run on non-selectable records when using the "Select all matching records" feature.

Instead of disabling deselection tracking across pages (which was the previous workaround and caused heavy performance/memory overhead on large tables), this PR:

  1. Restores deselection tracking when custom record selectability is defined.
  2. Filters out non-selectable records on the backend using the table's isRecordSelectable() check right after the selected records are resolved (for both collections and lazy collections).
  3. Safely computes totalSelectedRecordsCount using the resolved collection's count instead of a direct database query count, since the database count doesn't account for the PHP selectability closure.

Added a unit test in BulkActionTest to cover this scenario and verify that non-selectable records are ignored when executing bulk actions with isTrackingDeselectedTableRecords = true.

@people-sea people-sea left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain how this solves the query-level problem mentioned in your issue? SQL still cannot know about the PHP selectability closure, and this PR appears to filter only after records are resolved.

@hamdyelbatal122

Copy link
Copy Markdown
Contributor Author

@people-sea
You're right, SQL doesn't know about the PHP selectability closure, so the query itself still retrieves all records. Since the selectability check is a PHP closure, there's no way to filter at the database/query level.

The main issue this PR fixes is the performance/memory overhead. Previously, if custom selectability was defined, Filament disabled deselection tracking entirely (forcing Livewire to track and pass thousands of individual IDs over the wire, which hits memory limits on large tables).

By filtering the records in PHP after they are resolved (in memory or chunked via lazy collections), we can safely restore deselection tracking. This prevents Livewire from bloating with IDs, while ensuring the bulk action is never executed on non-selectable records.

@people-sea

Copy link
Copy Markdown
Member

I think this direction is unsafe: it re-enables deselection tracking while only filtering the resolved $records path, so query-based bulk actions can still include non-selectable records.

Copilot AI review requested due to automatic review settings June 18, 2026 11:56
@hamdyelbatal122

Copy link
Copy Markdown
Contributor Author

You're completely right @people-sea. I overlooked query-based bulk actions (like DeleteBulkAction with fetchSelectedRecords(false)), which would run on the unfiltered query.

To fix this, I updated getSelectedTableRecordsQuery() to evaluate selectability via lazy() chunking and restrict the query builder to the selectable record IDs. This ensures both model-based and query-based bulk actions are completely safe while keeping deselection tracking enabled.

I also added a unit test to verify this.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes bulk actions incorrectly operating on non-selectable records when using “Select all matching records” by ensuring selectability rules (checkIfRecordIsSelectableUsing() / isRecordSelectable()) are respected during bulk record resolution and execution.

Changes:

  • Re-enables deselection tracking even when a custom record-selectability callback is configured.
  • Filters resolved selected records through isRecordSelectable() for both collection and lazy-collection selection flows.
  • Adds unit tests to ensure non-selectable records are ignored for both record-based and query-based bulk actions when “select all” tracking is enabled.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/src/Tables/Actions/BulkActionTest.php Adds coverage ensuring “select all matching” bulk actions ignore non-selectable records (including query-based deletes).
packages/tables/src/Table/Concerns/HasBulkActions.php Allows deselection tracking even when custom selectability is configured.
packages/tables/src/Concerns/HasBulkActions.php Filters resolved selected records by selectability; updates selection query behavior to account for selectability.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +327 to +335
if ($table->checksIfRecordIsSelectable()) {
$selectableKeys = [];
(clone $query)->lazy(chunkSize: 1000)->each(function (Model $record) use (&$selectableKeys, $table) {
if ($table->isRecordSelectable($record)) {
$selectableKeys[] = $record->getKey();
}
});
$query = $table->getQuery()->whereKey($selectableKeys);
}
Comment on lines +371 to +379
if ($table->checksIfRecordIsSelectable()) {
$selectableKeys = [];
(clone $query)->lazy(chunkSize: 1000)->each(function (Model $record) use (&$selectableKeys, $table) {
if ($table->isRecordSelectable($record)) {
$selectableKeys[] = $record->getKey();
}
});
$query->whereKey($selectableKeys);
}
Comment on lines +201 to +204
// 2 published (selectable) and 1 unpublished (non-selectable)
$publishedPosts = Post::factory()->count(2)->create(['is_published' => true]);
$unpublishedPost = Post::factory()->create(['is_published' => false]);

@danharrin danharrin added bug Something isn't working pending review labels Jun 19, 2026
@danharrin danharrin added this to the v4 milestone Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working pending review

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Bulk actions execute on non-selectable records when using 'Select all matching'

4 participants