kpolicar/nova-date-range
kpolicar/nova-date-range is a Laravel Nova package that adds a date range field and picker for selecting start and end dates in resource forms and filters. It simplifies date range input in Nova admin panels with a clean, configurable UI for working with date-based records.
composer.json on GitHub and compare its Nova/Laravel constraints against your composer.json. With ~26 stars and a low opportunity score, this is almost certainly a Vue 2-era component, so it is a safe bet for Nova 2/3 and a coin-flip for Nova 4/5. Run composer require kpolicar/nova-date-range and pin it ("kpolicar/nova-date-range": "1.*") rather than floating *.src/DateRange.php tells you the real API surface — the column-configuration methods (->startColumn() / ->endColumn() in most builds, sometimes positional args to make()), the picker format default, and which resolveUsing/fillUsing hooks are already overridden.$table->dateTime('starts_at')->nullable()->after('id');
$table->dateTime('ends_at')->nullable()->after('starts_at');
use Kpolicar\DateRange\DateRange;
DateRange::make('Active period')
->startColumn('starts_at')
->endColumn('ends_at')
->format('Y-m-d'), // verify this method name in the source
dd($model->fresh()->only(['starts_at', 'ends_at'])). If you get two populated columns, the field is working and you can move on to index/filter concerns.The natural first use case is an event, campaign, or subscription "active window" on a resource form — anywhere you currently have two sibling Date fields that users expect to pick on one calendar.
Form-only field + computed index column. Because the field resolves to two attributes rather than one, Nova's index table has nothing to render under its name. The reliable pattern is:
DateRange::make('Active period')
->startColumn('starts_at')
->endColumn('ends_at')
->onlyOnForms(),
Text::make('Active period', fn () => $this->starts_at && $this->ends_at
? $this->starts_at->toDateString() . ' → ' . $this->ends_at->toDateString()
: '—')
->onlyOnIndex(),
Reuse via a base field. If every resource needs the same format and validation, subclass it once and keep ->format(), ->rules(), and ->help() in one place instead of repeating them across ten resources.
Pair it with a custom filter, not the built-in ones. whereBetween on a single column does not express "ranges that overlap this window." Write a NovaFilter that takes a range and queries the overlap:
$query->where('starts_at', '<=', $rangeEnd)
->where('ends_at', '>=', $rangeStart);
Single JSON column variant. If your schema prefers one column, override fillUsing/resolveUsing so the field serialises to {start, end} and you cast the attribute with 'period' => 'array' or a custom cast. Keep the write path in the field, not the model, so the picker stays a drop-in.
Use it anywhere a field goes. Actions and tool modals accept Nova fields, so the same component works for "bulk-shift the date range" style operations. Keep the action's handle() aware that $fields->starts_at and $fields->ends_at arrive as separate values, not one object.
{active_period: {start: ..., end: ...}} while your rules target starts_at/ends_at. ->rules('required|date') may attach to a key that never arrives. Prefer explicit per-column validation ('required_with:ends_at') or, on Nova 4+, ->rulesFor() — and confirm by dumping $request->all() on a failed submit.->sortable() maps to one attribute; there is no column named after the field, so the request is harmless but ineffective. Sort on starts_at explicitly or drop the affordance.fillUsing or accept the drift deliberately. Test with a non-UTC config('app.timezone').d/m/Y and the column is DATETIME, you'll get 0000-00-00 or an exception. Set the field format to something the DB accepts, or cast on the model and let the mutator parse.php artisan vendor:publish --force for the package's asset tag if one exists, then nova:publish.->withMeta([...]) to pass picker options through, subclassing for defaults, and a local fork are all cheaper than filing issues. Copy src/ into your own namespace if you need Vue 3 compatibility — at this star count, upstream fixes are unlikely to arrive on your timeline.composer.lock, and if you're on Nova 4/5, budget a day to port the component rather than assuming it will work out of the box.How can I help you explore Laravel packages today?