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

Laravel Nova Date Range Filter Laravel Package

pos-lifestyle/laravel-nova-date-range-filter

Add a date range filter to Laravel Nova resources with this lightweight package. Let users narrow resource lists by custom start and end dates, making date-based records easier to search and review in Nova admin panels.

View on GitHub
Deep Wiki
Context7

Getting Started

Install, then check the constraints before you trust the install. Run composer require pos-lifestyle/laravel-nova-date-range-filter, but open the vendor composer.json first. The last tagged release is July 2021, so its declared laravel/nova, PHP, and Laravel constraints predate Nova 5. If Composer resolves cleanly on your exact versions in a scratch branch, continue. If it fights the solver, stop and plan a fork rather than pinning your way around a constraint mismatch — the package is not going to move to meet you.

The repo is the documentation. There is no docs site and no meaningful examples beyond the README. Budget ten minutes, in this order:

  1. src/ — the filter class itself. This is your entire API surface: the apply() method, the $column property, make() / column(), and the Vue component name it registers. Read it end to end; it is short.
  2. resources/js/**/Filter.vue — confirms the request payload the browser actually sends. Is it {start, end}, {from, to}, or an array of two serialized dates? Date-only strings or ISO timestamps? Does it ship preset spans? The Vue file settles all of this in one pass.
  3. README.md — the canonical registration snippet. Copy it verbatim instead of reconstructing method names from memory; package APIs of this vintage drift from what you'd expect.

Register on exactly one resource first. Keep the blast radius to a single index screen until you've seen the popover render and the query run:

use PosLifestyle\DateRangeFilter\DateRangeFilter; // confirm namespace against src/

public function filters(Request $request)
{
    return [
        DateRangeFilter::make('Created')->column('created_at'),
    ];
}

Pick your most-queried timestamp and verify the SQL before you trust a row. created_at is the safe first target. Then instrument the query and read the compiled output:

DB::listen(fn ($q) => logger($q->sql.' '.json_encode($q->bindings)));

Day-one checks, all three of them:

  • The bindings are what you expect (right values, right shape, right order).
  • The upper bound includes the whole end day — not just midnight.
  • The date column is indexed.

Those three checks catch almost every bug report this filter will ever generate. Do them before anyone else uses the screen.

Implementation Patterns

Resource-local filter as the default. Registering inside one resource's filters() gives you the smallest surface, the most discoverable UI, and the easiest removal path if the package turns out to be incompatible with your Nova version. Only globalize when you have a concrete reason.

Global filter gated by canSee. For admin panels that want one consistent control across many resources, register in Nova::filters() (or the service provider) and gate visibility on context:

DateRangeFilter::make('Created')
    ->column('created_at')
    ->canSee(fn ($request) => $request->resource() === 'users');

One instance per date column. Nova ANDs independent filters together, so multiple instances let users narrow progressively instead of guessing a single combined bound:

DateRangeFilter::make('Created')->column('created_at'),
DateRangeFilter::make('Paid')->column('paid_at'),
DateRangeFilter::make('Shipped')->column('shipped_at'),

Label them distinctly. "Paid" versus "Paid At" matters when someone is scanning a filter panel under time pressure.

Presets are the actual day-to-day win. If the shipped Filter.vue exposes preset spans (last 7/30/90 days, this month), lean on them: support and ops users click "Last 30 days" rather than typing bounds. Keep the preset set short — every extra option adds decision time, and each preset is a boundary you have to verify against end-of-day semantics.

Reuse the same class on cards and metrics. This is the highest-leverage pattern in the package. Nova metric cards accept a $filters array; passing the same filter class in means "Last 30 days" means the identical range on the dashboard as on the index. One definition, consistent meaning across every admin surface. That alignment, not the widget, is the reason to adopt this.

Compose, don't branch. The range is just another clause on the index query, so it stacks cleanly with a status filter or Nova's built-in search. Resist putting business branching into shared registration code — subclass instead.

Subclass for related columns. If the date lives on a related model, the base package will not handle it. Extend the filter and override apply():

$query->whereHas('payments', fn ($q) => $q->whereBetween('paid_at', [$start, $end]));

Or join and filter the joined column. Either way, subclassing is the intended extension point.

Defaults without hiding the picker. Nova persists active filters per user per resource. If you want a default "last 30 days" window, seed it at registration time (or via a nova.filters callback) rather than inside apply() — that way the user's own selection wins once they pick one, and other resources stay unaffected.

Wrap registration behind a project convention. Put a small factory or base class in front of DateRangeFilter::make(...). When you eventually replace this unmaintained package with an in-house filter, the swap becomes a one-file change instead of a grep across every resource.

Gotchas and Tips

Nova compatibility is the dominant risk. No release since July 2021, zero dependents, no upstream support. Assume Nova 4 works and Nova 5 is unproven until you've demonstrated otherwise. Symptoms of breakage: blank popover, filter missing from the panel, or a Vue console error. Any of those means fork and repackage the component against your Nova version rather than debugging the shipped bundle.

The upper bound is almost certainly exclusive. A date-only 2024-01-31 handed straight to whereBetween silently drops everything after midnight on the last day. Confirm the payload shape from Filter.vue and, if it's date-only, normalize in an override:

$query->whereBetween($this->column, [
    Carbon::parse($value['start'])->startOfDay(),
    Carbon::parse($value['end'])->endOfDay(),
]);

This is the classic "the report is wrong" ticket. Verify it before your users do.

Never guess the payload. Before writing a subclass or a custom apply(), dd($value) once inside the method. Packages of this vintage have shipped both an array of two serialized date strings and a ['start' => ..., 'end' => ...] / ['from' => ..., 'to' => ...] map. Reading the Vue component settles it in one pass; guessing costs a debugging cycle.

Timezone drift generates real tickets. The browser sends naive local date strings; the database stores UTC (or your app timezone). A "today" filter can return different rows for a Sydney user than for a UTC server. Parse in the app timezone, convert to UTC before querying, and put the timezone in the filter label when it matters. The package's timezone option governs display — verify whether it also normalizes the query boundaries before trusting it.

Nullable columns silently drop rows. whereBetween skips NULL. Harmless for created_at; surprising for paid_at, where every unpaid invoice vanishes the moment anyone applies the range. Decide explicitly whether "no payment yet" belongs in the result and, if so, add ->orWhereNull($this->column) in your subclass.

Index the date column. The clause is cheap only with an index. Broad custom ranges against an unindexed created_at on a large admin table turn a filter into a full scan. Review query plans on representative row counts before shipping, and keep pagination in place.

Asset pipeline. Nova 4+ moved to Vite tooling; a Vue-2-era component may need repackaging. If published assets fail to load, forking and rebuilding the component against your Nova version is almost always faster than debugging the shipped JS.

Debugging checklist. Blank filter → confirm assets published and the Vue console is clean. Wrong rows → log bound values alongside compiled SQL and compare against startOfDay / endOfDay expectations. Different results across users → check timezone handling on both picker and server. Slow query → check the index before assuming a filter bug.

Fork and pin if you keep it. With no upstream maintenance, the sustainable path is to vendor the MIT code into an internal repo, pin the version, add compatibility tests against your Nova version, and name an owner. Budget the compatibility check into every Laravel/PHP/Nova bump — that recurring tax is the real cost of adoption, and it should be weighed against a one-time in-house build rather than against zero.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox