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

Nova Date Range Filter Laravel Package

ampeco/nova-date-range-filter

A Laravel Nova filter for narrowing resource index queries by date or datetime columns. Add it to a Nova filter panel so users can choose start and end dates and apply the range to supported model fields.

View on GitHub
Deep Wiki
Context7

Getting Started

Check two things before you type composer require. First, your Nova major version — this package's last release was 2021-09-28, which puts it in the Nova 3.x / Vue 2 era. On Nova 4 or 5, assume it will not build or will silently misbehave until proven otherwise. Second, whether native Nova filters already cover your case; if they do, adding this is a net-negative dependency.

Install and register. It's a plain Nova filter — no service provider, no migrations, no published config, no frontend build step of your own:

composer require ampeco/nova-date-range-filter

Then register it once per filtered column inside the resource's filters() method:

use Ampeco\NovaDateRangeFilter\DateRangeFilter; // confirm the FQCN after install

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

Where to look first, in this order:

  1. vendor/ampeco/nova-date-range-filter/src/ — the filter class. It is very short. Read apply() and whatever array holds the presets; those two are the entire behavioral surface.
  2. The bundled Vue component — you need to know exactly what shape it serialises into $value (expected: ['from' => ..., 'to' => ...]). Assert it in a test rather than trusting it.
  3. vendor/ampeco/nova-date-range-filter/composer.json — check the nova and laravel/framework constraints against your lockfile. A conflict here is a clean, fast "no" and you should not engineer around it.
  4. Your resource's filters() method — the only integration point you own.

First use case that pays for itself: an ops/support index view where someone needs "orders between Black Friday and Cyber Monday" or "invoices issued in Q3" without filing an engineering request. Because Nova persists active filters in the query string, a bookmarked URL like ?filters[date-range-filter][from]=2024-11-29&filters[date-range-filter][to]=2024-12-02 shares correctly with a colleague — that shared-URL behaviour is the actual day-to-day win, more than the picker itself.

Implementation Patterns

One filter instance per column. Filters are registered per resource, and column() binds a single date column. If three resources each need a created_at range, register three instances — don't try to share one filter class across resources with different columns unless you subclass it or pass the column through the constructor.

Custom apply() for anything that isn't a single column. The default column() covers the simple case; the moment your predicate is composite — an overlap test against starts_at/ends_at, or a status-dependent date field — override apply():

public function apply(Request $request, $query, $value)
{
    $from = $value['from'] ?? null;
    $to   = $value['to'] ?? null;

    return $query
        ->when($from, fn ($q) => $q->where('starts_at', '>=', $from.' 00:00:00'))
        ->when($to,   fn ($q) => $q->where('ends_at',   '<=', $to.' 23:59:59'));
}

Note the when() guards: a user can submit only a start date, or only an end date, and that partial-range case is exactly where naive implementations blow up with whereBetween(null, ...) or a silent no-op.

Overlap queries via a callback, not a column. For booking/reservation style data where the record's lifetime is starts_at → ends_at, you want "records that intersect the chosen window," which is starts_at <= $to AND ends_at >= $from. This is the pattern that justifies the package's apply() hook over a stock date filter.

Presets are the UX. Ship the presets your users actually ask for (today, yesterday, last 7 / 30 days, this month) and keep the custom start/end control as the escape hatch. A default range makes the filtered index useful on first page load instead of returning the whole table — but check what the default does to the generated query before you ship it; an unintended default is worse than no default.

Pair with metrics and lenses. Nova metrics take their own $range argument, so a range filter and a metric can tell two different stories about the same index. If finance asks "show me the orders and the total," implement the metric's range from the same from/to values rather than letting the filter apply to the table and the metric apply to a hardcoded window. Same for lenses: a lens that hardcodes whereBetween should read its bounds from the same source as the filter, or the two will disagree.

Vendor it, don't depend on it. For a component this size the defensible pattern is: copy the filter class into app/Nova/Filters/DateRangeFilter.php, keep the licence attribution, drop the Composer dependency, and own it. You get an identical UX with no abandoned dependency in your lockfile, and you can fix the partial-range and timezone issues in-house without waiting on a 2021 release. Treat the repo as reference source.

Test the query, not the UI. A feature test that instantiates the filter, calls apply() with a hand-built Request, and asserts the bound SQL/parameters is cheap and catches the failure mode that actually matters (see below):

$query = Order::query();
(new DateRangeFilter)->apply(
    Request::create('/', 'GET', ['filters' => ['date-range-filter' => ['from' => '2024-01-01']]]),
    $query,
    ['from' => '2024-01-01', 'to' => null]
);
$this->assertStringContainsString('"created_at" >= ?', $query->toSql());

Gotchas and Tips

The silent no-op is the real risk. After a Nova upgrade, the most likely failure is not a crash — it's the filter rendering fine while emitting a malformed or empty constraint, so the index quietly shows everything and finance trusts the numbers. Pin a test on the generated query scope and run it as part of your Nova upgrade checklist. Guard against $value arriving as a string, an array missing keys, or an empty array — all three occur in practice.

Nova 4/5 is a near-certain incompatibility. This is a 2021 filter with a Vue 2-era component; Nova 4+ changed the filter contract and frontend tooling. If composer require resolves cleanly, don't take that as proof — load the resource index in staging and confirm the filter actually constrains the query. Version conflicts in composer.json should be read as a fast rejection, not a puzzle to solve.

Partial ranges will bite you. Users routinely pick a start date and hit "Apply" without an end date. Always branch on both bounds individually. The 0.1.3 release note ("Trigger change if value isn't null") hints at exactly this territory — the component's change semantics around null values — so if you see the filter not applying until both dates are set, that's where to look.

Date-only vs. datetime boundaries. A date picker gives you 2024-03-31, not 2024-03-31 23:59:59. On a datetime column, where('created_at', '<=', '2024-03-31') excludes everything after midnight on the last day — off-by-one that nobody notices until a month-end report is short a day. Use whereDate() or append 23:59:59 explicitly, and decide whether your users think in application timezone or UTC. If the app stores UTC and your ops team is in a positive offset, a "today" preset can be wrong for eight hours of the day.

Check the index on the filtered column. A range predicate on an unindexed created_at is fine at 10k rows and painful at 5M. The filter makes this query easy to run repeatedly, which makes the missing index more visible — verify it independently; the package does not and cannot help.

Filter key naming matters for shared URLs. Nova derives the filter key from the class name; override public function key() to something stable and readable if bookmarked URLs are part of the workflow. Renaming the class later breaks every saved link your team has bookmarked — treat the key as public API.

Extension points are thin, which is fine. You can subclass the filter to override presets and apply(), and that's about it: no events, no config file, no middleware hooks. Anything beyond presets and the query predicate means editing the Vue component, which means forking. Given the size, forking is the expected path, not a failure.

Document the decision. A one-paragraph ADR recording "vendored from ampeco/nova-date-range-filter, validated against Nova X, fallback is native filters" is the cheapest insurance you can buy here — it stops the next engineer from assuming an unmaintained package is a supported dependency, and it stops the following one from re-investigating from scratch.

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
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle