Filament Resource Tables: Pagination, Sorting, and Search Without Custom JavaScript
Filament Resource tables give you server-side pagination, sorting, and search with zero JavaScript — here's a worked example, plus the state-persistence and indexing gotchas to check before production.
12 Oct 2025, 21:18 UTC

You need an admin table for a Laravel model: paginated, sortable, searchable, with bulk actions. The usual path is a Livewire component, a query builder, some query-string plumbing, and a pile of Blade. Filament's Resource tables collapse that into a PHP class where pagination, sorting, and search come from configuration rather than code you maintain.
The thesis: for standard CRUD admin tables, Filament's declarative table API is the fastest correct option — but you need to understand where its defaults stop, especially around state persistence and query performance.
What the Resource table gives you for free
A Filament Resource generates a Livewire-powered table wired to your Eloquent model. Server-side pagination, column sorting, and text search all work without touching JavaScript. Filament translates user input into Laravel query builder clauses: page and per-page become LIMIT/OFFSET, search becomes WHERE ... LIKE, and sort headers become ORDER BY.
That last point matters for debugging: nothing magical happens between the UI and the database. If a search feels slow, the answer is almost always a missing index, not a Filament setting.
A worked example: an orders table
Assume a standard Laravel 11 app with Filament v3 installed and an Order model. In app/Filament/Resources/OrderResource.php, the table definition looks like this:
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
class OrderResource extends Resource
{
protected static ?string $model = Order::class;
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('reference')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('customer.email')
->searchable(),
Tables\Columns\TextColumn::make('total')
->money('usd')
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable(),
])
->defaultSort('created_at', 'desc')
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
}Run php artisan make:filament-resource Order --generate from your project root (with your normal app user, no elevated permissions) to scaffold this, then adjust columns. Visit the generated admin page, open your browser's network tab, and interact with the table: each page change, sort toggle, or search keystroke fires a Livewire request, and you can confirm the server-side behavior by checking that the response reflects new LIMIT/OFFSET and ORDER BY clauses in the executed query (Laravel Debugbar or DB::listen in a local environment makes this visible).
The two sharp edges
State persistence. Pagination and sort state live in Livewire component properties ($page, sort state). They survive re-renders within a page, but a browser refresh or navigating away and back resets them unless you opt into persistence. Filament v3 supports session-based persistence for table state, but multi-tab scenarios can still surprise users — two tabs share the same session key. If your users live in these tables all day, test that workflow explicitly before shipping.
Query performance. Filament passes your searchable/sortable columns straight to the query builder. A searchable() column on an unindexed varchar over a few million rows will produce full table scans with leading-wildcard LIKE patterns. The fix is boring but necessary: add database indexes on frequently sorted columns, and consider restricting searchable() to columns that can actually use an index, or use searchable(query: ...) to customize the search clause for relationships or computed values.
Permissions ride along
Row-level and bulk actions respect the Resource's authorization methods (canView, canEdit, canDelete) and any model policy you have registered. That means the delete bulk action in the example above is automatically hidden from users lacking delete permission — no extra wiring, but verify it with a non-admin test account rather than assuming.
Closing
If your requirement is "a competent admin table over an Eloquent model," a Filament Resource table is the right default: declare columns, mark which are searchable and sortable, and let Livewire handle the round trips. Before calling it done, do three things: inspect one Livewire request to confirm the SQL matches your expectations, add indexes for every sortable/searchable column on large tables, and decide how you want table state to behave across refreshes. Those fifteen minutes of verification are the difference between a table that demos well and one that holds up in production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.