Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Saved Filter Laravel Package

jeffersongoncalves/filament-saved-filter

filament-saved-filter by Jefferson Goncalves lets Filament users save and reuse table filter combinations, making admin panels and dashboards faster to navigate. Persist frequently used filters and restore them with a click.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and migrate first.

composer require <vendor>/laravel-saved-views
php artisan vendor:publish --tag="saved-views-config"   # config
php artisan vendor:publish --tag="saved-filter-migrations"  # migrations, incl. the new columns
php artisan migrate

If you are upgrading to 1.6.0: bump jeffersongoncalves/laravel-saved-filter to ^1.10 before upgrading the parent package, then publish the saved-filter-migrations tag again and run php artisan migrate. This release adds the report timezone column and view/report IDs to the report context so every schedule links to its view. Skipping the migration produces SQL errors only when saving a view or scheduling a report — easy to miss if nobody clicks "Save view" or opens the schedule form. Optionally install laravel-report-url if you want email reports too large to attach delivered as temporary download links.

Where to look first (in order):

  1. config/saved-views.php — default visibility, table/column names, whether shortcuts are on, shortcut overrides.
  2. The trait/base class your resource uses to declare views (this is where ->keyBindings() and ->tabShortcuts() live).
  3. The Blade/Livewire component that renders the tabs and the Views menu — visibility labels (Preset / Shared / colleague name), the email-report slide-over, and the shareable ?view= link all surface there.
  4. For anything that is not a resource list page: your own table() method. ManageRelatedRecords pages and custom pages using InteractsWithTable only get saved views if you call configureSavedFilters() inside table().

First use case: make a table remember everything a user tuned. The cheapest win is enabling per-page and grouping persistence on an existing filtered table — a user sets 50 rows per page grouped by team, saves the view, and gets it back on next login without any custom code on your side.

// illustrative — confirm exact signatures against your version's docs
return SavedViews::make('orders')
    ->presets(fn () => [
        'mine'     => OrderFilter::mine(),
        'overdue'  => OrderFilter::overdue(),
    ])
    ->keyBindings()     // ship the default Ctrl/Cmd+Shift+S / +M
    ->tabShortcuts();   // opt-in Alt+1…9, Alt+] / Alt+[

Implementation Patterns

Choose tabs vs. dropdown by count, not by importance. Below ~5 views, tabs are faster to hit and make the active view obvious. Beyond that the tab bar wraps or scrolls and the dropdown becomes the real navigation. Both surfaces render the same visibility label, so keep the labels meaningful: Preset for code-defined, Shared for team-wide saves, and the colleague's name for personal shares. Pages without tabs now list their views in the Views menu.

Treat presets as code, saved views as data. Presets belong in a filter/query class under app/Filters (or your domain namespace) so they are versioned and testable. User-saved views are rows — never let a preset depend on a saved row's ID, or deleting a view breaks the preset. The same applies to report schedules: link them to the view row, not to a hard-coded preset name.

Persist grouping and per-page deliberately. A stored per-page value is user input that later reaches paginate(); validate it against your allowed page-size list on read, not just on write. Grouping keys should be canonical column/relation identifiers, not display labels, so renaming a column header doesn't strand old views.

Wire shortcuts once, at the component level, not per page.

->keyBindings([
    'save'   => 'mod+shift+s',
    'manage' => 'mod+shift+m',
])
// or disable entirely
->keyBindings(false);

Tab shortcuts are opt-in for a reason: ->tabShortcuts() is right for a dedicated reporting screen where the user lives in the table, and wrong for a CRUD index that sits inside a larger app with its own keyboard model.

Compose with your existing query-string/state layer — the URL is now part of the contract. The active view is in the URL as ?view=4, and links open it in dropdown mode too. Saved views should be the source of initial table state, not a competing store: hydrate the table from the view parameter on mount, let subsequent user changes live in normal component state until they explicitly save, and decide whether changing a filter clears view or leaves it as a "based on" marker. Filters present in the same link still win, so ?view=4&status=overdue means "view 4, but status forced to overdue".

Any table page, not just resource lists. ManageRelatedRecords pages (tabs included) and custom pages with InteractsWithTable get saved views once you call configureSavedFilters() in table(). Outside list pages views are keyed by the page class, so they deliberately do not mix with the resource list's views. Email reports remain a list-page feature.

Email reports are now a first-class workflow. The Views menu has a slide-over with the user's reports across every table of the tenant, so they can turn them on/off or remove them in one place. The schedule form carries a timezone, defaulting to the user's timezone attribute and then to the app timezone, and it shows what is currently scheduled for the picked view. The report email's Open the view button uses the same ?view= link, so a report and an in-app share point at the same thing.

Sharing is a UI concept backed by a policy. Decide who can share (creator only? anyone with update on the view?) and enforce it server-side — both when a view is shared and when a ?view= link is resolved. The visibility badge and an unguessable-looking URL are hints, not Authorization checks.

Handle imports as a user-facing choice. On import, the same-named view can be overwritten (the previous behavior), skipped, or imported as "Name (2)". Wire that choice into your import UI instead of assuming overwrite, and don't treat view names as identities in your tests.

Gotchas and Tips

  • The migration is the whole upgrade, again. laravel-saved-filter ^1.10 plus publishing saved-filter-migrations and running php artisan migrate are hard prerequisites for the timezone column and view/report linkage. Test the save and schedule paths explicitly after deploying, including on a database that already has saved views and reports.
  • Ctrl/Cmd+Shift+S is "Save page as" in Chrome and Firefox. The package should preventDefault(), but verify it in the browsers you support — and confirm you aren't shadowing a shortcut your users rely on. If in doubt, remap via keyBindings().
  • Alt+1…9 collides with native tab switching on Windows/Linux Firefox. Alt+]/Alt+[ also clash with editor and some browser/OS bindings. That is why tabShortcuts() is opt-in — enable it on focused, full-screen table pages rather than globally.
  • The "ignored while typing" guard needs auditing for custom inputs. Native <input> is covered, but contenteditable regions, rich-text editors, and third-party comboboxes often are not. Fire a test: focus each editor on the page and press Alt+3.
  • ?view= links are user- and tenant-scoped IDs. A link shared after an import, deletion, or across tenants can resolve to a missing or foreign view. Resolve defensively (fall back to the default view, log a warning) instead of 500-ing, and re-check authorization on every resolve — URL enumeration must not reveal other users' views or filter values.
  • Views do not cross page classes. A view created on the resource list will not appear on a related-records or custom table page, even if the columns look identical. That is by design; do not patch the key to "unify" them without understanding which page will later own the row.
  • Visibility labels go stale. A view shared by a colleague who later leaves the team will render a name resolved from a deleted user. Handle a null creator gracefully (fall back to Shared) rather than throwing in the view layer.
  • Shared views can leak data through filter values. A saved filter containing user_id = 42 or a customer email is visible to everyone it's shared with — and now also to anyone with the ?view= link. Audit what your filters actually serialize before enabling sharing broadly.
  • Report scheduling has its own timezone traps. The default is the user's timezone attribute, then the app timezone; a null attribute is the common case, not the exception. Test DST boundaries and what "next run" shows in the schedule form, and remember the timezone is stored per report, not per view.
  • Large reports need laravel-report-url. Without it, reports too large to attach have no download link to email. If you rely on report emails for big exports, add the dependency before rollout.
  • Repeated imports produce "Name (2)" and then "Name (3)". Treat names as display strings, keep ID/linkage as identity, and make sure your "skip" branch reports which views were skipped.
  • Schema drift silently breaks saved views. Removing or renaming a column invalidates any saved grouping or filter referencing it. Add a defensive resolution step (drop unknown keys, log a warning) instead of letting a stale view 500 the index page.
  • Debugging checklist when a save appears not to stick: is the migration run; is the save hitting both the original and new columns (->getAttributes() on the view row); is laravel-saved-filter actually on ^1.10 (check composer.lock, not composer.json); did the page call configureSavedFilters() if it isn't a list page; and is the table rehydrating per-page from the view or overriding it with a default.
  • Debugging checklist when a report doesn't arrive: schedule enabled for that view; timezone resolved as expected; laravel-report-url installed if the report is large; and the "Open the view" link actually resolving to a view the recipient can see.
  • Extension points worth knowing: the visibility/label resolver (for custom badge text or avatars) and the shortcut map (for a fully bespoke keybinding set). Both are cleaner than patching the published views.
  • Don't let this become your permissions model. Saved views decide how data is displayed; existing policies and global scopes decide what data exists. Keep those strictly separated, especially once team-shared views and emailed report links are in play.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib