Filament v3 Table Pagination: Simple vs Cursor Trade‑offs and Missing Hybrid Mode
0 reputation · 08 Dec 2021, 10:28 UTC
0 reputation · 08 Dec 2021, 10:28 UTC
Achieve performant pagination on large admin tables while retaining column sorting and direct page navigation for smaller result sets.
LengthAwarePaginator (OFFSET/LIMIT + COUNT(*)), which degrades on tables >100k rows.CursorPaginator) removes sorting UI and arbitrary page jumps, limiting UX for admin panels.Table::resolvePaginatorUsing() requires manual implementation and maintenance.Developers must decide whether to accept the performance penalty of simple pagination, sacrifice sorting with cursor pagination, or invest in a custom conditional paginator. The framework does not document a recommended pattern for this switch.
35275 reputation · 08 Dec 2021, 14:45 UTC
1. Recommended pattern for a conditional paginator
Table::resolvePaginatorUsing() to register a single custom paginator class.Model::count() or a pre‑cached value) and compare against a configurable threshold.LengthAwarePaginator (simple pagination). If above, delegate to a custom CursorPaginator that preserves Filament's UI hooks.perPage options and sorting parameters so the table behaves consistently.2. Preserving sorting UI with cursor pagination
orderBy clauses before applying cursor logic.id) to avoid ambiguous ordering.sortColumn and sortDirection to the underlying query.3. Future plans for a built‑in hybrid paginator
Create a service provider (e.g., App\Providers\FilamentTablePaginatorProvider) and register it in config/app.php.
public function boot()
{
Filament::serving(function () {
Table::resolvePaginatorUsing(function () {
return app(HybridPaginator::class);
});
});
}
Implement HybridPaginator.
class HybridPaginator implements PaginatorContract
{
protected int $threshold;
protected QueryBuilder $query;
protected array $sort;
public function __construct(int $threshold = 50000)
{
$this->threshold = $threshold;
}
public function paginate(QueryBuilder $query, int $perPage, string $pageName = 'page'): LengthAwarePaginator|CursorPaginator
{
$this->query = $query;
$this->sort = $this->extractSort();
$count = $query->count();
if ($count <= $this->threshold) {
return LengthAwarePaginator::resolveUsing($query, $perPage, $pageName);
}
// Apply deterministic ordering
$query->orderBy($this->sort['column'], $this->sort['direction']);
$query->orderBy('id');
return new CustomCursorPaginator($query, $perPage, $pageName);
}
protected function extractSort(): array
{
// Retrieve sort info from Filament's request or state
// Return ['column' => 'name', 'direction' => 'asc']
}
}
Define CustomCursorPaginator to extend Filament's CursorPaginator and expose the same sorting UI hooks.
class CustomCursorPaginator extends CursorPaginator
{
public function toArray(): array
{
// Ensure the paginator data includes the sort state for the UI
$data = parent::toArray();
$data['sort'] = request()->input('sortColumn', 'id');
$data['direction'] = request()->input('sortDirection', 'asc');
return $data;
}
}
Optional: Create a config file (e.g., config/filament/paginator.php) to allow developers to tweak the threshold and other options.
Test the implementation with both 10,000 and 200,000 row tables. Verify that:
OFFSET and COUNT(*) for ≤ 50k rows.SELECT with LIMIT and no OFFSET for >50k rows.Table::resolvePaginatorUsing() accepts a closure that returns a paginator instance.50k is a starting point.If you have a specific database engine or indexing strategy that might affect cursor performance, let us know so the recommendation can be adjusted.
Register a single custom paginator via Table::resolvePaginatorUsing() that internally switches between LengthAwarePaginator and a cursor‑based paginator. Preserve sorting UI by applying the sort to the query before cursor logic and exposing the sort state in the paginator's data. Until Filament introduces a built‑in hybrid strategy, this custom approach is the most maintainable solution for large admin tables.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.