When you make table columns toggleable, Filament saves the user's visibility choices in the session. That works fine until they log out, switch devices, or the session expires. Then everything resets to defaults.
If your users customize their table views and expect those choices to stick around, you need to persist them somewhere more permanent. Here's how to save column preferences to the database instead.

Under the hood, Filament's HasColumnManager trait on the Livewire component has two methods that handle persistence:
loadTableColumnsFromSession() reads column state from the sessionpersistTableColumns() writes column state to the sessionThe data itself is a simple array of column names, visibility flags, and ordering. We just need to redirect that storage to a database table.
First, a table to store preferences per user and per Filament page:
php artisan make:migration create_user_table_preferences_table
database/migrations/xxxx_xx_xx_create_user_table_preferences_table.php
Schema::create('user_table_preferences', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('table_key'); $table->json('columns'); $table->timestamps(); $table->unique(['user_id', 'table_key']);});
The table_key will hold the session key Filament already generates (an MD5 hash of the Livewire component class), so each resource page gets its own preference row.
php artisan make:model UserTablePreference
app/Models/UserTablePreference.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class UserTablePreference extends Model{ protected $fillable = [ 'user_id', 'table_key', 'columns', ]; protected function casts(): array { return [ 'columns' => 'array', ]; } public function user(): BelongsTo { return $this->belongsTo(User::class); }}
Instead of modifying every List page individually, we'll create a reusable trait:
app/Filament/Concerns/PersistsColumnsInDatabase.php
<?php namespace App\Filament\Concerns; use App\Models\UserTablePreference; trait PersistsColumnsInDatabase{ protected function loadTableColumnsFromSession(): array { $preference = UserTablePreference::query() ->where('user_id', auth()->id()) ->where('table_key', $this->getTableColumnsSessionKey()) ->first(); return $preference?->columns ?? $this->getDefaultTableColumnState(); } protected function persistTableColumns(): void { if (! $this->getTable()->persistsColumnsInSession()) { return; } UserTablePreference::query()->updateOrCreate( [ 'user_id' => auth()->id(), 'table_key' => $this->getTableColumnsSessionKey(), ], [ 'columns' => $this->tableColumns, ], ); }}
Two things to note:
loadTableColumnsFromSession() falls back to getDefaultTableColumnState() when no database record exists, same as the original session logic.persistTableColumns() still respects the persistsColumnsInSession() flag. If you've disabled persistence on a specific table, this won't write to the database either.app/Filament/Resources/Orders/Pages/ListOrders.php
<?php namespace App\Filament\Resources\Orders\Pages; use App\Filament\Concerns\PersistsColumnsInDatabase; use App\Filament\Resources\Orders\OrderResource;use Filament\Actions\CreateAction;use Filament\Resources\Pages\ListRecords; class ListOrders extends ListRecords{ use PersistsColumnsInDatabase; protected static string $resource = OrderResource::class; protected function getHeaderActions(): array { return [ CreateAction::make(), ]; }}
That's it. Column visibility and ordering preferences now survive logouts, session expiry, and device switches.
If you want this on every List page without adding the trait one by one, you can create a base class:
app/Filament/Pages/BaseListRecords.php
<?php namespace App\Filament\Pages; use App\Filament\Concerns\PersistsColumnsInDatabase;use Filament\Resources\Pages\ListRecords; class BaseListRecords extends ListRecords{ use PersistsColumnsInDatabase;}
Then extend BaseListRecords instead of ListRecords in your resource pages.
The getTableColumnsSessionKey() method generates a unique key per Livewire component class, so you don't need to worry about collisions between different resources. If you also use reorderableColumns(), the ordering is stored in the same columns JSON, so it gets persisted to the database too.
A few of our Premium Examples: