optimistdigital/nova-detached-filters
A Laravel Nova package that lets you detach filters from the default filter dropdown and show them separately, keeping applied filters persistent across page loads and navigation. Useful for building shareable, always-visible filtered views of Nova resources without re-applying filters each time.
Install and pin — this package's release history tracks Nova majors, so a floating constraint will eventually resolve to something your Nova can't run:
composer require optimistdigital/nova-detached-filters:3.0.1
The service provider is auto-discovered. There are no migrations, no publishable config, and no asset build step on your side — the frontend ships precompiled and is registered through Nova's asset API. Don't add a vendor:publish step to your runbook out of habit just because other Outl1ne plugins have one.
Where to look first. Open vendor/optimistdigital/nova-detached-filters/src/ and confirm two things before you write a line of application code:
optimistdigital, the repo is outl1ne, and the wrapper class has historically been DetachedFilter. Read the source — a wrong use statement is the most common first-five-minutes failure with this package and it fails as a class-not-found, not a helpful error.First use case. Pick the one filter your operators open the dropdown for on every visit — order status, ticket state, a created-date range — on your busiest resource, and lift it out:
use OptimistDigital\NovaDetachedFilters\DetachedFilter; // verify against vendor source
use App\Nova\Filters\OrderStatus;
public function filters(Request $request)
{
return [
DetachedFilter::make(new OrderStatus),
new CreatedWithinRange, // stays in the native dropdown
];
}
Reload the resource index. You should see the filter outside the dropdown with Nova's native apply/clear behavior intact. That is the entire core loop of the package — everything else is a variation on it.
Wrap once, never twice. A filter must appear exactly once in filters() — either wrapped or raw. Returning both gives you a filter rendered in two places and a query constraint that may be applied twice. Put this in code review as a hard rule; it's the most likely defect in a PR that touches this package.
Mixed arrays are the normal shape. Detachment is per-filter, not per-resource. The idiomatic layout is a short toolbar of two to four always-visible filters, with the long tail (soft deletes, admin-only scopes, obscure ranges) left in the dropdown. Pick the convention once — "status and date range detach, everything else stays" — and apply it uniformly. Inconsistent placement across resources generates more support load than the dropdown ever did.
Authorization stays on the filter, not in filters(). Because you're just constructing instances, you keep full control. Use the filter's own canSee() rather than branching in the filters() method:
return [
DetachedFilter::make(
(new Region)->canSee(fn ($request) => $request->user()->isRegionalAdmin())
),
new CreatedWithinRange,
];
This preserves Nova's native permission semantics — the package is presentation-only, so canSee() and apply() behave exactly as they do in the dropdown. Don't reimplement visibility logic in the resource.
Stickiness belongs in default(). Detached filters reset on fresh navigation because the state lives in the query string. If a filter is detached specifically because operators always want "open" orders, give the filter class a default(). The package deliberately does not persist state — it delivers visibility, not persistence.
Order by visual weight, not by code order. Detached filters render in array order. Put the narrow, high-frequency control first (a select or toggle) and the wide ones last (date ranges, multi-selects). This is what keeps the toolbar from wrapping awkwardly on a 13" laptop, which is where most back-office work happens.
Validate against your worst custom filter, not your best. Filters with custom Vue components and custom apply() implementations render detached as long as they render in the dropdown. The risk is your component, not the package: if it assumes it lives inside Nova's dropdown container (absolute positioning, dropdown-relative widths, portal-based selects), it will look broken detached. Spike with your most complex custom filter.
Compose with lenses deliberately. Both lenses and detached filters are query-string driven, so they compose. The real-world failure mode is ordering: if a lens already constrains the same column, the detached filter looks like it "doesn't work." Verify each detached filter against your primary lens before shipping.
Placement beyond the index. If you move the toolbar onto a dashboard or tool screen, the same rules apply — you're rendering the filter's own component, so state handling and canSee() follow it. Keep a single source of truth for which filters are detached rather than duplicating the list between a resource and a custom page.
Test it as UI, not as backend. Feature tests won't catch regressions here because the failure mode is rendering, not query logic. Add a cheap smoke assertion per resource with detached filters:
$this->get('/nova/resources/orders')->assertOk()->assertSee('...');
It won't catch layout breakage, but it will catch the Nova-upgrade class of failure where the component fails to mount — which is exactly the failure you're most exposed to with a 63-star, single-maintainer package.
This package breaks when Nova moves — budget for it. The existence of 3.0.1 as a type-compatibility fix tells you the maintenance model: the maintainer keeps up, reactively. Pin an exact version, and gate your next Nova major upgrade on a manual pass over every resource using detached filters. With 63 stars and zero dependents, nobody will surface breakage before you do.
Asset staleness is the classic "it worked in staging" failure. Assets are precompiled and registered through Nova's script/style API. After any deploy touching vendor/, run the standard Nova cache and asset-clearing commands. If the toolbar renders unstyled or not at all, check the network tab for the package's script/style and clear compiled views before you start reading PHP — nine times out of ten it's a stale asset manifest.
Filters do not survive navigation. Clicking into a detail page and back loses the selection unless the filter defines a default(). If a stakeholder asked for "filters that stay applied," this package does not deliver it. Set that expectation before shipping, or you'll ship it and immediately field a bug report.
Don't let the toolbar become the new dropdown. Six detached filters gives you a wall of controls that pushes the table below the fold. Cap it at three or four per resource, and treat "we need a fifth" as a signal to revisit the resource's information architecture rather than a reason to add another control.
Narrow viewports degrade visibly. Nova's index is responsive; a custom toolbar is only as responsive as its container. Check at ~1280px and ~1024px during the spike. On tablets and phones, detach fewer filters — or leave that resource on stock Nova behavior entirely.
Audit reset behavior yourself. Verify the reset/clear affordance in your detached layout clears all filters, detached and dropdown together. A reset that silently leaves a hidden dropdown filter applied is a genuinely confusing operator bug and easy to miss in review.
Debugging order that actually saves time: (1) does the filter appear correctly in the dropdown when unwrapped? (2) is it returned exactly once from filters()? (3) does the FQCN resolve from the actual vendor source? (4) do the package's assets load in the network tab? (5) is this Nova major supported by the pinned version? Working that order keeps you out of the vendor source when the answer is a cache clear.
The extension point is the fork, not configuration. The package is deliberately thin with no meaningful config surface. If you need something it doesn't provide — persisting selections, grouping filters into a sidebar block, custom dashboard placement — read the source (small enough to finish in one sitting) and either fork under MIT or wrap it in your own Nova component. An in-house equivalent for a single resource is roughly a day of work; name that as a costed fallback before adoption, not after.
Pilot on two or three high-traffic resources first. Install in staging, wrap the filters, brief one admin group on where filters now live, and watch for a sprint. Install cost is an afternoon; the recurring cost is Nova-upgrade validation. Discover whether that's acceptable on a small surface before it's threaded through a dozen resources.
How can I help you explore Laravel packages today?