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

Filament Date Range Laravel Package

codewithkyrian/filament-date-range

Adds a date range picker field to Filament forms, letting users select start and end dates through a clean, user-friendly interface. It integrates smoothly with Filament panels and Laravel apps for quick date-range input and filtering.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require codewithkyrian/filament-date-range

The service provider is auto-discovered; the package registers its Alpine/Flatpickr assets via FilamentAsset::register(), so no php artisan filament:assets is strictly required in a standard Vite theme. If your panel uses a custom theme compiled with @vite, run php artisan filament:assets (and re-run after upgrades) or the range widget renders as a plain text input.

Where to look first

  1. src/Forms/Components/DateRangePicker.php — the field class; this is the source of truth for the fluent API (minDate, maxDate, displayFormat, firstDayOfWeek, rangeSeparator, etc.). Skim the public methods before reading the README.
  2. src/Tables/Filters/DateRangeFilter.php — the filter, including the default whereBetween behaviour and the query() extension hook.
  3. resources/views/ — the Blade view showing which Alpine component and state keys are actually bound.
  4. resources/js/ — the Flatpickr wrapper; check this when formatting or locale behaviour surprises you.

First use case — a "created between" table filter

This is the 80% case and the reason to install the package at all:

use CodeWithKyrian\FilamentDateRange\Tables\Filters\DateRangeFilter;

public static function table(Table $table): Table
{
    return $table
        ->columns([
            TextColumn::make('name'),
            TextColumn::make('created_at')->dateTime()->sortable(),
        ])
        ->filters([
            DateRangeFilter::make('created_at')
                ->label('Created between'),
        ]);
}

Two clicks, one date range, whereBetween on the column. That's the whole value proposition — you get a single filter control instead of two DatePickers glued together with custom query logic.

Implementation Patterns

Form field for reports / exports

use CodeWithKyrian\FilamentDateRange\Forms\Components\DateRangePicker;

DateRangePicker::make('period')
    ->label('Reporting period')
    ->displayFormat('d M Y')
    ->minDate(now()->subYears(2))
    ->maxDate(now()->addYear())
    ->required()
    ->columnSpanFull(),

The state arrives in your action() / handle() as an array, not a string — read it defensively:

[$start, $end] = [$data['period']['start'] ?? null, $data['period']['end'] ?? null];

Normalising boundaries before querying

The single most common bug with any range picker: the end date is midnight, so whereBetween silently drops the final day. Normalise in one place — a small helper or a ->dehydrateStateUsing() on the field:

DateRangePicker::make('period')
    ->dehydrateStateUsing(fn (?array $state) => [
        'start' => filled($state['start'] ?? null) ? Carbon::parse($state['start'])->startOfDay() : null,
        'end'   => filled($state['end'] ?? null)   ? Carbon::parse($state['end'])->endOfDay()   : null,
    ]),

Custom filter queries

Override the default whereBetween when the column isn't a plain datetime (e.g. a JSON path, a related table, or a date column you want to compare against endOfDay):

DateRangeFilter::make('created_at')
    ->query(function (Builder $query, array $state): Builder {
        return $query
            ->when($state['start'] ?? null, fn ($q, $v) => $q->whereDate('created_at', '>=', $v))
            ->when($state['end'] ?? null,   fn ($q, $v) => $q->whereDate('created_at', '<=', $v));
    });

Passing the range into a header action

A frequent pairing: range filter on the table + "Export" action that reuses the active filter state rather than duplicating inputs. Pull it from the filter form's live state in the action closure, or hoist the range into a table-level property so both the filter and the action read the same value.

Reusing the widget outside tables

Both classes extend the standard Filament field/filter base classes, so the usual toolbox applies: ->hidden(), ->visible(fn () => auth()->user()->isAdmin()), ->columnSpan(), ->live() + ->afterStateUpdated(). You can drop the field into an Action modal form (Actions\Action::make('export')->form([...])) exactly like any other field.

Testing

Use Livewire's Filament helpers rather than driving Flatpickr through the DOM — the Alpine widget is not what you want to assert against:

livewire(ListOrders::class)
    ->filterTable('created_at', ['start' => '2026-01-01', 'end' => '2026-01-31'])
    ->assertCanSeeTableRecords($januaryOrders)
    ->assertCanNotSeeTableRecords($decemberOrders);

That test is worth writing before you rely on the filter, because it locks in the state key names (start / end) that the package uses.

Gotchas and Tips

  • Confirm the state keys. Everything downstream depends on whether the field emits ['start' => ..., 'end' => ...] or ['from' => ..., 'to' => ...]. Check resources/views/ before you write dehydrators or queries — a silent key mismatch yields an always-empty filter that looks like a bug in your query.
  • Filament v3 only. There is no v4 branch. If you're planning a Filament 4 upgrade, treat this field as something you'll reimplement (it's a thin Flatpickr range wrapper) rather than something that will follow you.
  • Empty vs null state. Clearing the picker can yield null, [], or ['start' => null, 'end' => null] depending on path. Guard with filled($state['start'] ?? null) everywhere; never $state['start'] directly.
  • Timezone drift. Flatpickr works in browser-local time; whereBetween on a UTC-stored column will disagree at the edges. For anything billing- or SLA-related, set the panel timezone explicitly and convert on dehydrate, or you'll get "off by one day" reports from users east of UTC.
  • Date-only columns need whereDate. whereBetween('date', ['2026-01-01', '2026-01-31']) on a DATE column is fine; on a DATETIME column it is not. Don't use the same filter config for both.
  • Assets after deploy. New asset hashes mean browsers cache the old JS. If the range UI works locally and not in staging, it's almost always a stale asset / missing filament:assets step, not the package.
  • Custom theme + Tailwind purging. Filament ships a safelist for package views; if you compiled a theme against an older Filament, the picker's calendar may render unstyled. Add the package view path to your theme's content globs.
  • Version pinning. 40 stars, single maintainer, zero dependents. That's fine for a UI widget — but pin the constraint ("^1.0"), read the diff before minor bumps, and keep the fallback in mind: two DatePickers + a ->query() closure reproduces ~90% of this package in an afternoon if it ever goes stale.
  • Extension point. Subclassing DateRangePicker and overriding getView() / the Alpine component name is the cleanest way to add presets ("Last 7 days", "This quarter") — do that in one subclass used across your panel rather than sprinkling ->afterStateUpdated() handlers on every form.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core