ghanem/multiple-date-picker
A Laravel/PHP package for adding a multiple-date picker to forms and interfaces, letting users select several dates at once. Useful for scheduling, booking, and date-range scenarios without building multi-date selection logic from scratch.
composer require ghanem/multiple-date-picker, then confirm the package auto-registers (Nova packages usually ship a NovaServiceProvider-style provider via composer.json extra.laravel.providers, or expose a publish tag). If a dist/ folder isn't committed, run npm install && npm run prod inside vendor/ghanem/multiple-date-picker — Nova fields can't be compiled from your app's Vite config.src/ first, then dist/js/field.js. With 0 stars and 0 dependents there is no blog post, no Stack Overflow trail, and no one to ask. The source is your documentation; the two things you need to confirm before writing a single migration are (a) the storage format the Vue component emits and (b) the laravel/nova constraint in composer.json — Nova 4 vs Nova 5 incompatibility is the most likely reason this package silently renders nothing.$table->json('blackout_dates')->nullable();
protected $casts = ['blackout_dates' => 'array'];
BlackoutDate or Availability resource where an admin picks a handful of dates in one click instead of spawning repeaters or a third-party calendar UI. Best fit is a short, bounded list of discrete dates — this is not a date-range picker and it is not a scheduler.Register it in a Nova resource:
use Ghanem\MultipleDatePicker\MultipleDatePicker;
MultipleDatePicker::make('Blackout Dates', 'blackout_dates')
->nullable()
->rules(['array']),
Field definition on a JSON column. Keep the display compact and push formatting to a displayUsing callback so index views don't explode into a wall of ISO timestamps:
MultipleDatePicker::make('Blackout Dates')
->displayUsing(fn ($value) => collect($value ?? [])
->map(fn ($d) => \Carbon\Carbon::parse($d)->format('d M Y'))
->implode(', '))
->hideFromIndex(),
hideFromIndex() is often the right call — a multi-date field renders unpredictably in Nova's table cells, and detail views are where the picker earns its keep.
Validation. Nova applies rules to the field's single value, so nested rules go in as an array of strings with wildcards. This is the pattern that actually works:
->rules(['array'])
->rules(['*' => 'date'])
Do not write 'blackout_dates.*' => 'date' — Nova scopes the rule for you and the prefixed form silently fails to attach to anything.
Storage-format flexibility via fillUsing/resolveUsing. If you inherit a table that stores comma-separated dates (legacy), or you want to normalise to Y-m-d before saving while keeping the picker in ISO-8601, override at the field level rather than forking the package:
MultipleDatePicker::make('Blackout Dates')
->resolveUsing(fn ($value) => $value ? explode(',', $value) : [])
->fillUsing(function ($request, $model, $attribute) {
$model->{$attribute} = collect($request->input($attribute, []))
->map(fn ($d) => \Carbon\Carbon::parse($d)->toDateString())
->unique()->sort()->values()->all();
});
This is the main extension point and it's the reason the package is usable at all — the field class is thin, so overriding resolution and filling covers almost every mismatch between what the Vue component emits and what your column expects.
Filtering. Nova's built-in filter builders don't understand JSON arrays. Write a boolean/select filter that uses whereJsonContains:
public function apply(Request $request, $query, $value)
{
return $query->whereJsonContains('blackout_dates', $value);
}
If you need "contains any of these dates", loop and orWhereJsonContains inside a nested where closure so the rest of the filter chain stays intact.
Pairing with a dependent field. ->dependsOn('some_field', ...) works as with any Nova field, but the picker needs a full re-render to pick up the new value — if the calendar doesn't refresh, force it with :key in a small custom field wrapper rather than patching the vendor component.
Form-only vs. detail. A practical split is ->onlyOnForms() for the picker plus a Text field with a displayUsing summary on index/detail. It avoids having to reason about how the component behaves in read-only mode, which is the least-tested path in most low-adoption Nova fields.
2025-03-04T00:00:00.000Z) rather than Y-m-d. If you're storing into a DATE column or doing string comparisons, UTC offset drift will move dates by a day for users west of Greenwich. Normalise in fillUsing and don't trust the component.->sortable() will fail on MySQL JSON. ORDER BY meta->... on a JSON column errors unless you have a generated column and index. Either drop sortable() or add a stored generated column and sort on that.->searchable() is similarly a trap — Nova's default search does a LIKE on the column. It "works" on MySQL JSON in the sense that it doesn't throw, but results are unreliable. Filter instead.<input>, the JS bundle isn't loaded. Check php artisan nova:publish ran, check public/vendor/ghanem exists, and check the browser console for a failed app.js — this failure mode looks identical to a broken field.composer update can break you and nobody will file the issue. Use "ghanem/multiple-date-picker": "1.0.*" (adjust to the actual tag for the 2025-01-26 release) and only bump deliberately.->default([]) alongside ->nullable() if the column is NOT NULL — the component can submit null when the user clears all selections, and a null written to an array cast comes back as null, not [], so every count() downstream needs a guard.->rules(['array']) alone permits an empty array — that's usually what you want, but if at least one date is required use ->rules(['array', 'min:1']) and add a custom message, otherwise the error text is Nova's generic "The field is invalid."Field subclass, the cleanest escape hatch if the release goes stale is to copy src/ into app/Nova/Fields/ and point the component at your own published asset directory. At 0 stars, assume you may end up owning this code and budget for it in the estimate.How can I help you explore Laravel packages today?