outl1ne/nova-detached-filters
Nova Detached Filters lets you pull Laravel Nova filters out of the default filter dropdown and render them detached elsewhere on the page, keeping frequently used filters visible and one click away for faster resource browsing.
Three steps, roughly ten minutes.
Install and pin.
composer require outl1ne/nova-detached-filters
The service provider is auto-discovered — no config/app.php edit. Immediately pin the resolved constraint in composer.json ("outl1ne/nova-detached-filters": "3.0.1") rather than leaving a floating ^3.0, and make sure your laravel/nova constraint is explicit too. This package patches Nova's filter internals, not a stable public API.
Read vendor/outl1ne/nova-detached-filters/src before you read anything else. It is a small package (a service provider, one or two PHP classes, one Vue component). The functional surface is one wrapper/flag plus a trait — five minutes in src/ tells you exactly which symbol marks a filter as detached, and that is faster and more reliable than skimming a README for a package this small. Confirm the class name before copy-pasting any snippet, including the ones below.
Wire it into one resource. In app/Nova/User.php, wrap the filter you want promoted and leave the rest untouched:
use Outl1ne\NovaDetachedFilters\...; // whatever `src/` shows
public function filters(NovaRequest $request)
{
return [
DetachedFilter::make(new UserStatus), // rendered as a panel on the index
new UserRole, // stays in the dropdown
];
}
Load the resource index. The status filter should now render as an always-visible panel above the table, behave identically to its dropdown version, and write the same ?user_status=... query string.
First use case: take your highest-traffic internal resource (tickets, orders, users) and promote exactly one filter — status or owner. Not three. Ship it, watch it for a week, then decide about a second.
Mixed mode is the default, not a fallback. Detaching is per-filter-instance, so promoting one filter does not touch the others. Keep the long tail — rare date ranges, geo filters, admin-only flags — in the dropdown. A useful convention is "detached = a filter an operator touches on most visits; dropdown = a filter used weekly or less." Document that rule in one line in the resource class.
Adopt incrementally, one resource at a time. Never roll it across every resource in a single PR. Pick the resource where operators already complain about clicking, promote 1–2 filters, and let it sit. Expand only on an articulated win. This keeps the blast radius to a single file per resource and makes rollback a one-line delete.
Reuse the filter class; don't fork it. A detached filter is the same Filter subclass executing the same apply() — same query, same ->withMeta(), same defaults. If you find yourself writing a second "detached version" of a filter, you've misread the package. The only thing that changes is where the component mounts.
Order matters and follows filters(). Detached panels render in declaration order. Put the most-used filter leftmost/first; on narrower viewports the ordering is what protects the useful controls from being pushed off.
Custom filter components: verify visually first. Built-in Nova filters (BooleanFilter, SelectFilter, DateFilter, RangeFilter) render fine. Custom Vue filter components are the one place to check by hand — mount the component, apply a value, confirm the panel collapses/expands and the applied indicator is readable at panel width rather than dropdown width. If a custom component assumes a narrow container, that is your breakage, not the package's.
Treat the URL as the contract. Nova serializes filter state into the query string; detached filters participate in that unchanged. Your integration test is manual but quick: apply a detached filter → paginate → sort → copy the URL into a fresh tab → hit "reset filters." All four must show consistent state. Anything else generates "the numbers look wrong" tickets.
Codify a one-page convention alongside the first adoption: which filters are detached per resource, the rationale, and a query-cost check (does the filter's apply() do anything pathological on a large table?). Add "re-verify detached filters" to the Nova upgrade runbook the same day.
composer update cannot silently orphan it.php artisan nova:publish / vendor:publish, and that your deploy script runs the same. A half-published state shows as a blank index or a Vue console error that looks like the package is broken when it's actually a caching problem.Filter classes already do. If the ask is "save this filtered view," this package is not the answer and you should say so early.How can I help you explore Laravel packages today?