kisame76 / filament-db-table-state
Persist Filament table state (filters, sort, search, column order & visibility) per user in the database, across devices and sessions.
Package info
github.com/Kisame76/filament-db-table-state
pkg:composer/kisame76/filament-db-table-state
Requires
- php: ^8.2
- filament/filament: ^4.0|^5.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- livewire/livewire: ^3.5|^4.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^2.9|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0|^4.0
README
Filament DB Table State
Persist Filament table state — filters, sort, search, column order and column visibility — per user in the database, instead of only the session.
Filament can already persist table state with ->persistFiltersInSession(), ->persistSortInSession(), ->persistColumnsInSession() and friends. But the session is per-browser and short-lived: clear cookies, switch device or let the session expire and the state is gone.
This package mirrors that same state into a database row keyed by the user, so a user's filters and column layout survive new sessions and follow them across devices. By default it upgrades every table you already flag with ->persist*InSession(); flip one switch to cover every table.
How it works
Filament writes table state to the session (when the ->persist*InSession() flags are on). This package hooks into Livewire globally and:
- Seeds the session from the user's saved DB row on component boot — before Filament's
bootedInteractsWithTable()reads it. - Snapshots the session back to the DB at the end of the request — after Filament has written the latest state.
It reuses Filament's own session keys (getTableFiltersSessionKey(), getTableSortSessionKey(), getTableColumnsSessionKey(), …), so it stays compatible with how Filament stores state. Each table gets its own small row per user, holding just that table's state.
Requirements
- PHP 8.2+
- Filament v4 or v5
- Livewire v3.5+ / v4
Installation
composer require kisame76/filament-db-table-state
Publish and run the migration (creates the table_states table):
php artisan vendor:publish --tag=filament-db-table-state-migrations php artisan migrate
The migration adds a user_id foreign key with a cascade delete to your users
table. Using UUID/ULID keys or a custom users table? Edit the published migration
before running migrate — it has inline comments showing what to change.
That's it. Any table that uses Filament's session persistence (->persistFiltersInSession(), ->persistSortInSession(), ->persistColumnsInSession(), …) now also persists to the database — surviving new sessions and following the user across devices. To enable it for every table automatically, set auto_enable_persistence => true (see below).
Configuration (optional)
php artisan vendor:publish --tag=filament-db-table-state-config
return [ // Master switch. When false the package does nothing. 'enabled' => env('DB_TABLE_STATE_ENABLED', true), // Default false = only tables you've flagged with ->persist*InSession() // are mirrored. Set true to flip those flags on for EVERY table. 'auto_enable_persistence' => false, // Storage. user_column is a string so it supports integer and UUID keys. 'table' => 'table_states', 'user_column' => 'user_id', // Auth guard used to resolve the current user (null = default guard). 'guard' => null, ];
Custom user resolution
Need a custom guard or extra scoping? Set a resolver from any service provider's boot():
use Kisame76\FilamentDbTableState\Support\TableStatePersister; TableStatePersister::resolveUserIdUsing(fn () => auth('admin')->id());
What persists, and when
The package mirrors whatever Filament writes to the session — it never touches the URL:
- Column layout (visibility + order): Filament persists this to the session by default, so it is always mirrored to the database, on every table, while the package is enabled.
- Filters, sort, search: Filament keeps these in the URL query string by default (not the session), so they are mirrored only once you opt the table in (or enable every table — see below).
Default: opt in for filters, sort & search
Out of the box (auto_enable_persistence => false), turn on Filament's native
session persistence for the tables you care about:
public function table(Table $table): Table { return $table ->persistFiltersInSession() ->persistSortInSession(); // column layout already persists by default }
Those tables now persist filters and sort to the database as well — surviving new sessions and following the user across devices.
Filament's session-persistence methods
These are the native Filament Table methods this package mirrors to the
database. The defaults are Filament's own — note that only the column layout is
persisted out of the box:
| Method | Persists | Default |
|---|---|---|
->persistFiltersInSession() |
Filters | off |
->persistSortInSession() |
Sort | off |
->persistSearchInSession() |
Table search | off |
->persistColumnSearchesInSession() |
Per-column searches | off |
->persistColumnsInSession() |
Column visibility + order | on |
Enable all of them on a single table:
public function table(Table $table): Table { return $table ->persistFiltersInSession() ->persistSortInSession() ->persistSearchInSession() ->persistColumnSearchesInSession() ->persistColumnsInSession(); }
Every method also accepts false to turn it off — handy when
auto_enable_persistence is on and you want to exclude one table, e.g.
->persistColumnsInSession(false).
Persist every table automatically
Set auto_enable_persistence => true to flip Filament's session persistence on
for every table via Table::configureUsing() — no per-table changes needed.
// config/db-table-state.php 'auto_enable_persistence' => true,
When this is on, opt a single table back out by chaining the native flags in its
table() method — your call wins:
public function table(Table $table): Table { return $table ->persistFiltersInSession(false) ->persistColumnsInSession(false); }
Custom global combinations
auto_enable_persistence => true is a shortcut that turns everything on for
every table. For any other global combination — say, persist filters on every
table but never persist columns — leave auto_enable_persistence => false and
configure Filament yourself in your app's AppServiceProvider::boot():
use Filament\Tables\Table; public function boot(): void { Table::configureUsing(function (Table $table): void { $table ->persistFiltersInSession() // filters on every table → mirrored to the DB ->persistColumnsInSession(false); // never persist the column layout }); }
The package's hook mirrors whatever ends up in the session, so that is all you
need: filters now persist to the database everywhere, columns nowhere. Keep
auto_enable_persistence => false so the package doesn't flip its own defaults
on top of yours.
Turning it off
Set enabled => false to switch the whole package off — no hook, no mirroring.
Notes & caveats
- Each table has its own small row per user (keyed by user and table), so reads and writes only ever touch that one table's state.
- Persistence is fail-safe: any error while reading/writing state is swallowed so it can never break a page.
- Two browser tabs editing the same table can race on the last write; the most recent request wins.
License
MIT — see LICENSE.md.