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

marshmallow/nova-date-range-filter

A Laravel Nova filter that lets users narrow resource listings by a date range using native from/to pickers. Drop it into any Nova resource to filter records on date or datetime columns, with a clean UI and simple setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a single-purpose Nova filter: it puts a date-range picker into a resource's filter panel and applies a whereBetween-style predicate to one column. There are no migrations, no config file, and no service provider work beyond package discovery. The v2.3.0 release over 2.2.2 is additive only — translation files for EN, UK, and NL — so on the day-to-day level it's a Composer bump with no code changes on your side.

composer require marshmallow-packages/nova-date-range-filter
<?php
// app/Nova/Order.php
use Marshmallow\NovaDateRangeFilter\DateRangeFilter;

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

Open the order index, pick a preset (or custom start/end), confirm the list narrows, and confirm the URL now carries a base64 filters query parameter. That query parameter is your first-class debugging tool for the rest of the package's life — save a bookmarked index URL with a filter applied and you have a reproducible case.

Before wiring it into more than one resource, read three things in vendor/marshmallow-packages/nova-date-range-filter/:

  1. src/DateRangeFilter.php — the exact make() argument order (label-first vs. column-first) and the shape of $value that apply() receives. This is the single thing developers get wrong from memory; copy it from source rather than from a blog post.
  2. apply(Request $request, $query, $value) — this method is the package as far as your data is concerned. Read it to determine whether the end date is inclusive, whether it uses whereBetween on raw datetimes, and whether it touches timezones at all. Write those three answers into the PR description when you add the filter.
  3. lang/ — added in v2.3.0 and the authoritative list of translatable strings. Check whether labels are PHP lang keys passed into the Vue component or JSON files consumed directly, because that determines where you add a locale that isn't EN/UK/NL.

First use case worth shipping: replace the "operator hand-edits ?created_at=>=2025-01-01 in the URL" workflow on orders, invoices, or activity logs with a "Last 30 days" preset that survives a page refresh and can be bookmarked for a daily standup view. It's a one-line registration on a resource you already own, and if it's wrong you delete the line.

Implementation Patterns

Per-resource registration, once, then extracted into a trait. The filter is declared in filters(), one entry per resource. The moment a third resource needs it, centralise the definitions so labels and columns can't drift apart:

<?php
namespace App\Nova\Concerns;

use Marshmallow\NovaDateRangeFilter\DateRangeFilter;

trait HasDateRangeFilters
{
    protected function dateRange(string $label, string $column): DateRangeFilter
    {
        return DateRangeFilter::make($label, $column);
    }

    protected function createdFilter(): DateRangeFilter
    {
        return $this->dateRange(__('Created'), 'created_at');
    }
}

This trait is also your rollback plan — one file to change if you ever fork or remove the dependency.

Subclass and re-implement apply() whenever the semantics matter. The package's job is to hand you a range; what that range means to your reporting is your business rule. Extend the filter so end-date inclusivity and timezone conversion are explicit, readable, and testable rather than inferred:

<?php
namespace App\Nova\Filters;

use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Marshmallow\NovaDateRangeFilter\DateRangeFilter as BaseFilter;

class CreatedDateRange extends BaseFilter
{
    public function apply(Request $request, $query, $value)
    {
        // $value shape: read src/DateRangeFilter.php — do not guess.
        [$start, $end] = $this->normalise($value);

        return $query->whereBetween('created_at', [
            CarbonImmutable::parse($start)->startOfDay()->utc(),
            CarbonImmutable::parse($end)->endOfDay()->utc(),
        ]);
    }
}

Mirror the parent constructor after reading src/ — the column is likely a protected property you can reuse rather than hardcoding 'created_at' again.

Compose with a status or customer filter, and index for the composition. Nova ANDs the apply() calls of every active filter, so the resulting SQL is where status = ? and created_at between ? and ?. That query wants a composite index (status, created_at), not two single-column indexes. Check this against your highest-volume resources — orders and activity logs — before rolling the filter out broadly.

Control visibility rather than duplicating resources. filters() receives the Request, and Nova filters support ->canSee(), so finance-only date filters on a shared invoice resource don't clutter the UI for support agents. This is the cleanest way to put a date filter on a resource with mixed audiences.

Default the common range where your Nova major supports it. If ->default() is available to you, set "Last 30 days" on high-traffic ops indexes. Otherwise the operator re-picks the same range every session — and eventually stops using the filter at all.

Test apply() directly instead of clicking through the browser. Instantiate the filter, hand it a crafted Request and a query builder, and assert on ->toSql() and bindings. This is where boundary and timezone bugs surface — the UI will happily hide them:

$filter = new \App\Nova\Filters\CreatedDateRange('Created', 'created_at');

$query = $filter->apply(
    Request::create('/', 'GET'),
    Order::query(),
    ['start' => '2025-03-01', 'end' => '2025-03-31'] // use the real decoded shape
);

// Assert on bindings, not just SQL
$this->assertStringContainsString(
    '2025-03-31 23:59:59',
    $query->getBindings()[1]
);

Add one fixture record at 23:59:59 on the end date and one just before 00:00:00 on the start date. Those two records are the whole test suite's value.

Round-trip the URL as an integration check. Bookmark an index URL with a filter applied and add it to your smoke-test notes. It exercises Nova's serialisation, the Vue component, and PHP apply() in one step — a Nova upgrade that breaks any of the three will fail visibly against a saved URL.

Gotchas and Tips

  • End-date inclusivity is the highest-impact unknown in this package. whereBetween('created_at', ['2025-03-01', '2025-03-31']) silently excludes everything after 2025-03-31 00:00:00. An operator reading a month-end report cannot see that the numbers are short. Verify with the 23:59:59 fixture before trusting any export built on top of this filter — and re-verify after every version bump, not just after you install.

  • Timezone handling is yours, not the package's. Nova displays timestamps in the user's locale but stores them however your app stores them. If the picker sends a bare Y-m-d and the column holds UTC datetimes, an operator in Europe/Amsterdam filtering "Today" gets yesterday's evening. Normalise explicitly in a subclass — never rely on the package doing it.

  • Don't reach for whereDate() in your overrides. It wraps the column in a function and kills index usage. Range predicates against raw columns stay sargable. If you need day-granularity semantics, startOfDay()/endOfDay() on the bindings gives you the same result without touching the column.

  • Debug from the URL, not the UI. Nova serialises selected filters into a base64 filters query parameter. Decode it to see exactly what the front-end sent, before PHP ever runs:

    json_decode(base64_decode($request->query('filters')), true);
    

    Half of all "the filter does nothing" reports end at that step — usually with an empty $value or a date format you didn't expect.

  • Never edit language files in vendor/. composer update wipes them. If the package ships a publish tag for lang/, publish into lang/vendor/... and commit. If it doesn't, copy the en file upstream-style, add your locale, and open a PR — for a single-maintainer package that is the cheapest way to avoid permanent divergence.

  • "UK" in the v2.3.0 changelog is ambiguous between Ukrainian (uk) and United Kingdom (en_GB). Check the actual filename in lang/ before telling anyone the admin is localised. Missing keys fall back to English silently, so the filter still works — it just looks inconsistent, and nobody files a bug for it.

  • Nova upgrades are the breakage trigger, not this package's releases. Nova's front-end asset pipeline is the coupling point. After a Nova major bump, re-check that the filter component renders and that no asset republish is needed. Put a line in the Nova upgrade runbook; this package will not warn you.

  • The extension points are deliberately narrow: subclass apply() for query semantics, publish lang/ for copy, use canSee() and filters() for per-role visibility. There is no config file — if you want different presets, you are overriding PHP, not configuring. Budget accordingly when someone asks for "just a custom shortcut".

  • Check composer.json for your pinned Nova major before you start. If your Nova version isn't declared, stop; a constraint mismatch discovered after you've wired six resources is an avoidable afternoon.

  • Bus factor of 1 (13 stars, 0 dependents). Keep the registration to a handful of lines behind a trait so removal or a vendored fork is a sub-hour operation. Everything this package touches is admin-layer — nothing in your domain model or API depends on it — which is exactly what keeps adoption cheap and rollback trivial. Keep it that way.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console