ymsoft/filament-table-presets
A Filament package for saving and restoring table presets—let users store column layouts, filters, sorting, and other table settings, then switch between named views instantly without reconfiguring tables each time.
Install and register the plugin in three steps, then verify with one real table before rolling it out.
composer require ymsoft/filament-table-presets
php artisan vendor:publish --tag="filament-table-presets-config"
php artisan vendor:publish --tag="filament-table-presets-migrations"
php artisan migrate
Then register it on the panel (not globally) so you can gate it per panel/environment:
// app/Providers/Filament/AdminPanelProvider.php
use Ymsoft\FilamentTablePresets\TablePresetsPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(TablePresetsPlugin::make());
}
Where to look first. In this order: the README's usage block, src/TablePresetsPlugin.php (what the fluent configure methods actually expose), the published config/filament-table-presets.php (ownership/scoping/storage options), and the migration (the column set tells you what a preset can actually store — usually table identifier, user/tenant id, name, and a JSON state payload). Read the migration before the README; it's the most honest description of what is persisted.
First use case. Pick your widest ListRecords page — a support inbox, orders, or audit log with 15+ columns and several filters. Add the preset action to that page's header and let two or three internal users save a named view ("My open tickets", "Refunds this week"). That single page will surface every configuration question you have (scoping, defaults, column-name churn) in an afternoon. Do not start with the resource your customers use.
Wire it to the page, not the resource. Presets are page state, and the plugin is normally attached at the Livewire page level (via the trait/action the plugin exposes) or through a header action returned from getHeaderActions(). Attaching at the resource level and hoping it cascades to every page (ListRecords, relation managers, dashboard widgets) is the most common mis-step. Add it to ListRecords, confirm it works, then decide about relation managers separately.
Treat it as a wrapper around Filament's own table state. The plugin is fundamentally persisting the same array Filament already builds from column visibility, filters, sort, search, and (depending on config) pagination. Two consequences follow:
->persistFiltersInSession(), you now have two persistence layers fighting over the same state. Pick one. Presets are the better choice for anything a user will want to return to next week; session persistence is only for transient in-flow filtering.Defaults matter more than the save button. The feature people actually feel is "the table opens the way I left it." Expose a per-user default preset (the plugin typically offers a fluent toggle and a way to resolve the default) and apply it on page mount. Without a default, presets become a feature nobody discovers — a dropdown people scroll past.
Seed presets for onboarding. Ship 2–3 system presets per critical resource created via a seeder or a Preset::create([...]) call in your provisioning flow, especially if your app onboards tenants. New users landing on a table with a "Ready to ship", "Needs review" preset already present get value on day one; a blank dropdown does not.
Scope deliberately. Per-user presets are the safe default. Shared/team presets need an authorization story: who can rename, overwrite, or delete a preset another user created? Add a policy on the plugin's model (published config usually lets you swap the model) rather than patching the package. If you're multi-tenant, confirm the preset query is tenanted — a preset leaking across tenants is a real data-disclosure bug, not a cosmetic one.
Version and test it like application code. Presets are user data; a plugin upgrade that changes the stored payload shape will silently break saved views. Write one Livewire test per critical page asserting that mounting with a given preset yields the expected visible columns, and pin the plugin version in composer.json ("ymsoft/filament-table-presets": "~1.0") so a minor bump can't land unreviewed.
Column renames silently orphan state. If the JSON payload keys on column names (it almost certainly does), renaming amount to total_amount doesn't error — presets just stop restoring that column and users see a view that's quietly wrong. Grep your presets table after any column rename, and prefer stable, non-user-facing column names from the start.
The migration is the #1 first-run failure. Forgetting vendor:publish --tag="filament-table-presets-migrations" in a fresh environment (or in CI) produces a "table not found" that looks like a plugin bug. Add the publish+migrate to your deploy script, not just your local setup docs.
Debug by reading the row, not the UI. When a preset won't restore, select state from <presets_table> where id = ? and compare against a freshly captured state. Nine times out of ten the difference is a filter key that no longer matches a getName() you changed, or a null where the current code expects an array. Then check storage/logs/laravel.log and run php artisan filament:cache-components — stale component caches are a frequent red herring in Filament generally.
Config quirks to check explicitly. Read the published config for: the table/model binding (if you can override the Eloquent model, do it — adding a tenant_id or soft deletes to a vendor model via migration-only is painful), whether pagination and search are included in the saved state (many users are surprised that page 4 is restored), and whether there's a per-panel enable flag. Set the enable flag false in local if you don't want preset rows polluting your dev DB.
Index the presets table. These are read on every table render for the current user. A composite index on (user_id, table_identifier) — add it in your own migration if the package doesn't — keeps list pages fast once you have real users. Watch for an N+1 if the plugin loads presets per row in any relation-manager context.
Extension points beat forks. The usual levers are: swap the model via config, publish and override the action/view, and listen for the package's events (if any) rather than editing vendor/. Only 17 stars and 0 dependents means the package is early — you are likely to be the first person to hit an edge case, so keep your customizations in your own service provider and be prepared to send a PR upstream. If the package turns out to be under-maintained, the fallback is a small trait of your own: capture $this->table->getFilters(), column visibility, and sort into a JSON column, and restore in mount(). That's a ~100 line replacement, so don't let this dependency reach a point where it can't be swapped.
How can I help you explore Laravel packages today?