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

Custom Date Filter Laravel Package

keizah7/custom-date-filter

A Laravel package that adds a configurable date filter to your admin panels, letting users narrow records by custom ranges, presets, or single dates. Simple to register, easy to style, and flexible enough for any resource.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose Nova filter package with no stars, no dependents and a score of 0 — so treat the README as a hint, not a contract, and read the source before you build on it.

1. Install (verify the tag first)

composer show keizah7/custom-date-filter --all
composer require keizah7/custom-date-filter

If Packagist has no tagged release you'll need to pin a branch or add a VCS repository entry:

"repositories": [{ "type": "vcs", "url": "https://github.com/keizah7/nova-date-filter" }],
"require": { "keizah7/custom-date-filter": "dev-main" }

2. Look here first, in this order

  • composer.json — the illuminate/* and laravel/nova constraints tell you which Nova major it targets. A Nova 3 constraint on a Nova 4/5 app is the most common reason this kind of package "doesn't work".
  • src/ — the filter class. Confirm whether it extends Laravel\Nova\Filters\Filter and what shape $value arrives in (apply() signature).
  • resources/js/ — if there's a Vue component, the filter has a custom UI and you'll need npm run build / nova:publish semantics. If there's no JS, it's a plain select-based filter and Nova renders it for you.
  • README.md — usually a screenshot and one snippet.

3. First use case: date-range filter on a resource index

Register it like any Nova filter:

// app/Nova/Order.php
public function filters(NovaRequest $request)
{
    return [
        \Keizah7\CustomDateFilter\CustomDateFilter::make(),
    ];
}

Then load /nova/resources/orders, open the filter dropdown, pick a date, and confirm the resulting URL contains a filters query param. That URL is the contract — copy it into a failing test if the results look wrong.

Implementation Patterns

Preset ranges via options() — the highest-value pattern for day-to-day Nova work, and the one users actually ask for. Most date filter packages expose this; if this one doesn't, add it in a subclass:

public function options(NovaRequest $request)
{
    return [
        'Today'        => 'today',
        'Last 7 days'  => 'last_7',
        'This month'   => 'this_month',
        'Last month'   => 'last_month',
        'This year'    => 'this_year',
    ];
}

public function apply(NovaRequest $request, $query, $value)
{
    return match ($value) {
        'today'      => $query->whereDate('created_at', today()),
        'last_7'     => $query->where('created_at', '>=', now()->subDays(7)),
        'this_month' => $query->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()]),
        'last_month' => $query->whereBetween('created_at', [
            now()->subMonthNoOverflow()->startOfMonth(),
            now()->subMonthNoOverflow()->endOfMonth(),
        ]),
        'this_year'  => $query->whereYear('created_at', now()->year),
        default      => $query,
    };
}

Explicit from/to range with two inputs. When the filter exposes a range, $value is typically an array of strings from the frontend:

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

    return $query
        ->when($from, fn ($q) => $q->where('created_at', '>=', Carbon::parse($from)->startOfDay()))
        ->when($to,   fn ($q) => $q->where('created_at', '<=', Carbon::parse($to)->endOfDay()));
}

Sensible default so the index isn't a full table scan — default() pre-populates the filter, which matters once the table is large:

public function default()
{
    return 'last_7';
}

Reuse across many resources. Rather than registering the package class on fifteen resources, extract the date logic into a base class or trait and let each resource supply the column:

abstract class DateColumnFilter extends Filter
{
    abstract protected function column(): string;

    public function apply(NovaRequest $request, $query, $value)
    {
        return $query->whereBetween($this->column(), [
            Carbon::parse($value['from'])->startOfDay(),
            Carbon::parse($value['to'])->endOfDay(),
        ]);
    }
}

Qualify the column when the index query joins. Nova lens and relationship queries often introduce joins; unqualified created_at will throw ambiguous column errors. Use $query->qualifyColumn('created_at') or orders.created_at.

Test the filter directly. You don't need a browser to verify behaviour:

$filter = new CreatedAtRange;
$query  = $filter->apply(NovaRequest::create('/'), Order::query(), ['from' => '2024-01-01', 'to' => '2024-01-31']);

$this->assertSame(42, $query->count());

Guard with canSee() when only certain roles should filter by date — cheaper than a policy and keeps the dropdown tidy.

Gotchas and Tips

  • End-of-day truncation. whereBetween('created_at', ['2024-01-01', '2024-01-31']) silently drops everything after midnight on the 31st. Always ->endOfDay() the upper bound, or the bug report will read "records are missing for the last day."
  • whereDate kills your index. On MySQL, whereDate('created_at', $d) compiles to DATE(created_at) = ?, which can't use the created_at index. On a large table prefer an explicit half-open range: >= startOfDay() and < startOfDay()->addDay(). Reach for whereDate only on small tables or where correctness beats speed.
  • Timezone drift. Nova sends the date the user picked; Eloquent stores UTC by default. Either store and compare in UTC consistently, or convert with Carbon::parse($value, config('app.timezone'))->utc(). A filter that's off by a day for users east of UTC is the classic symptom.
  • $value shape is not guaranteed. Depending on whether the filter renders as a select, a date input, or a multi-select, $value can be a string, an array, a JSON-encoded string, or null when the user clicks Reset. Always null-guard and return $query unchanged on empty input.
  • Nova version pinning. Check the laravel/nova constraint in composer.json against your installed Nova. Nova 4 removed a lot of the frontend scaffolding Nova 3 packages depend on; a filter with a bundled Vue component from the Nova 3 era will usually render blank in Nova 4/5.
  • Custom UI means asset publishing. If src/ ships a .vue file, Nova needs the compiled JS. Run php artisan nova:publish and rebuild assets; a filter that appears in the dropdown but never fires is nearly always a missing or stale bundle.
  • Filter state lives in the URL. ?filters=base64... means bookmarks and shared links carry the date range. Useful, but it also means a user's "Today" link shows stale results tomorrow — prefer preset keys over resolved dates for anything you expect people to share.
  • Reset semantics. Nova's Reset Filters passes null/empty for every filter; confirm apply() handles it before shipping, or you'll get a Carbon::parse(null) exception on the reset click.
  • Small package, no safety net. Zero stars and zero dependents means no community to catch breakage and no maintenance guarantee. Given how little code a date filter needs, the pragmatic move is often to read src/, copy the ~40 lines into app/Nova/Filters/, and drop the dependency — you keep full control over timezone and end-of-day behaviour, and you're not exposed if the package stops working with a Nova upgrade. If you do keep it, pin the exact version in composer.json and add a feature test around the filter so an upgrade can't break it silently.
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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel