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 Filter Laravel Package

64robots/nova-date-filter

Add date filtering to Laravel Nova resources with a customizable date filter. Choose the column, pick from preset ranges or custom dates, and filter index queries without writing extra query logic.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm your Nova version before anything else. The package is archived, so its composer.json constraint (typically Nova 2/3-era) is the real gate. Check it first:

composer show 64robots/nova-date-filter

Then install (pin the version — there will be no patches):

composer require 64robots/nova-date-filter

Where to look first:

  1. README.md — the option/range signature is the whole API surface.
  2. src/ — the base DateFilter class (the apply() implementation and how it reads the selected value).
  3. resources/js/ — the Vue component that renders the range dropdown. This is what breaks on newer Nova, so read it before assuming the UI works.

First use case — a "Created" range filter on a resource index. Generate a filter and extend the package's base class rather than Nova's:

namespace App\Nova\Filters;

use 64robots\NovaDateFilter\DateFilter;

class CreatedAtFilter extends DateFilter
{
    public $name = 'Created';

    // Column the filter constrains — verify the exact property/method
    // name against the README; the package exposes one way to declare it.
    protected $field = 'created_at';

    public function options(): array
    {
        return [
            'Today'        => [now()->startOfDay(), now()->endOfDay()],
            'Last 7 days'  => [now()->subDays(6)->startOfDay(), now()->endOfDay()],
            'This month'   => [now()->startOfMonth(), now()->endOfMonth()],
        ];
    }
}

Register it on the resource:

public function filters(Request $request): array
{
    return [new CreatedAtFilter];
}

If the dropdown renders and selecting "This month" narrows the index, you have the happy path. Everything below is about the edges.

Implementation Patterns

One reusable base filter, many subclasses. The package pays off when you stop writing apply() by hand. Create an intermediate class in your app that fixes the column and shares option sets, then subclass per resource:

abstract class BaseDateFilter extends DateFilter
{
    protected function commonRanges(): array
    {
        return [
            'Today'       => [now()->startOfDay(), now()->endOfDay()],
            'Yesterday'   => [now()->subDay()->startOfDay(), now()->subDay()->endOfDay()],
            'This week'   => [now()->startOfWeek(), now()->endOfWeek()],
            'Last 30 days'=> [now()->subDays(29)->startOfDay(), now()->endOfDay()],
        ];
    }
}

Give every filter a sensible default. A null selection should mean "no constraint", not "today". Nova hydrates filters on first load; if the package's apply() runs with an empty value, make sure it short-circuits:

public function apply(Request $request, $query, $value)
{
    if (blank($value)) {
        return $query;
    }

    return parent::apply($request, $query, $value);
}

Use whereBetween, not whereDate. The underlying implementation matters for indexes. If you override apply(), prefer:

$query->whereBetween($this->field, [$start, $end]);

over whereDate($this->field, ...), which prevents MySQL from using the index on timestamp columns.

Relationship columns. The package filters a column on the resource's base query. For related tables, compose with whereHas:

public function apply(Request $request, $query, $value)
{
    return $query->whereHas('customer', function ($q) use ($value) {
        // delegate the range logic, or reimplement the between clause here
    });
}

Pair it with a second filter for compound ranges. Two date filters (created/updated, or start/end) work fine as long as both are single-select and each names a distinct column — Nova ANDs them. Avoid stacking two instances of the same filter class with different names unless you've verified the component's unique-key handling, which is a known weak spot in archived Nova filters.

Card integration. Filters registered on a resource are also available to metrics cards built on that resource. If a card's numbers look wrong when a filter is active, check whether the card calls $request->filters — the package only affects the resource query, not card aggregates, unless the card opts in.

Testing. Don't drive the Vue component in tests. Instantiate the filter directly and assert the SQL:

$query = User::query();
(new CreatedAtFilter)->apply(request(), $query, json_encode([...]));
$this->assertStringContainsString('between', $query->toSql());

Gotchas and Tips

It's archived — plan the exit. No Nova 4/5 support (Vue 3, Vite build, changed filter-component contract). Symptoms: the filter appears in the dropdown but selecting an option does nothing, or the JS bundle fails to register. Before adopting, check composer.json's nova constraint against your installed version. If you're on a modern Nova, the realistic options are (a) fork and port the component, (b) replace it with a plain Nova Filter whose options() returns labels and whose apply() does a switch on the value — roughly 30 lines, no JS needed, and usually the right call. Archived date filters are the cheapest thing in Nova to hand-roll.

Value serialization. Nova sends filter values through the query string. The package likely JSON-encodes the selected range, which means $value in apply() is a JSON string, not an array. Don't assume the shape — dd($value) in apply() once and confirm before writing logic against it.

Timezone drift is the #1 bug. Nova has a timezone config; your app and database may not agree. "Today" computed in app timezone then compared against UTC-stored timestamps produces off-by-one-day results at the boundaries. Pick one convention (store UTC, do range math in the user's timezone) and keep it consistent between the filter options and the query.

Empty and stale selections. A filter value persisted in the URL can reference a range that no longer makes sense; and Nova sometimes hydrates a previously selected value on page load. Guard apply() against unexpected keys in $value with a match/array_key_exists check that falls back to unconstrained rather than throwing.

apply() must return the query. Easy to forget after an early return in a guard clause. A missing return produces a "call to member function on null" far from the cause.

Index the column. A date filter on an unindexed column is a full scan on every index page load once the table grows. Check EXPLAIN on the generated query; whereBetween on an indexed timestamp column is the difference between 5ms and 500ms.

Extension points, in order of preference:

  • Override apply() in subclasses — safe, no build step.
  • Override options() — safe, keep ranges relative (now(), not hardcoded dates).
  • Publish/override the Vue component — last resort; requires recompiling and will not survive a Nova upgrade.

Debugging checklist: dd($this->field, $value) at the top of apply(); confirm the filter is in filters() for that specific resource (filters don't inherit across resources); check the network tab for the filters query param on the index request; and if the UI is dead but the query param is correct, the problem is the Vue component, not PHP.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager