gabelbart/nova-improved-date-field
An improved date field for Laravel Nova that gives you more control over how dates and times are displayed and edited in your admin panel, with flexible formatting options and a cleaner, more intuitive picker experience for your resources.
Install
composer require gabelbart/nova-improved-date-field
No manual provider registration is normally required — Nova fields are plain PHP classes resolved at resource-definition time. If the package ships a service provider for publishing JS assets, run:
php artisan vendor:publish --provider="Gabelbart\NovaImprovedDateField\FieldServiceProvider"
php artisan nova:publish
Where to look first
vendor/gabelbart/nova-improved-date-field/src/ — the field class itself. This is where the real API lives; the README on GitLab is thin, but the class exposes everything the Vue component accepts.vendor/gabelbart/nova-improved-date-field/resources/js/ — the picker component and its flatpickr options. Any option you can't find a PHP setter for is usually still reachable by inspecting how the component consumes field.* props.jacobfricke/nova-configurable-date-field) — note the package is a repackaged fork, so the upstream README/source is your fallback documentation.First use case
The canonical use is a published_at / starts_at datetime column where you want a display format, an input format, and consistent timezone behaviour across form, index, and detail views:
use Gabelbart\NovaImprovedDateField\ImprovedDate;
ImprovedDate::make('Published At', 'published_at')
->format('DD/MM/YYYY HH:mm') // what the user sees in index/detail
->pickerFormat('d/m/Y H:i') // what flatpickr shows in the form
->rules('nullable', 'date')
->sortable(),
Pair it with a model cast ('published_at' => 'datetime') and you have a working field in under five minutes.
Because you'll use the same format everywhere, don't repeat format()/pickerFormat() across resources. Put a factory on a base resource or a trait:
trait HasDateFields
{
protected function dateTime(string $name, ?string $attribute = null): ImprovedDate
{
return ImprovedDate::make($name, $attribute ?? Str::snake($name))
->format('DD/MM/YYYY HH:mm')
->pickerFormat('d/m/Y H:i')
->rules('nullable', 'date')
->sortable();
}
}
Then $this->dateTime('Published At', 'published_at') in every resource. When the client asks for a format change, it's one edit.
The single most important habit with this field: format() is a display concern (index, detail, and often the resolved value), while pickerFormat() is a flatpickr token string. They use different token languages:
| Token | format() (PHP-ish / display) |
pickerFormat() (flatpickr) |
|---|---|---|
| Day | d |
d |
| Month | m |
m |
| Year | Y |
Y |
| Hour (24h) | H |
H |
| Minutes | i |
i |
| Seconds | s |
S |
Note i vs S for seconds and the fact that flatpickr's i is minutes — this trips people up constantly. If your index shows the wrong time, you mixed the two up.
For date (not datetime) columns, use the field but force a date-only picker — otherwise users can attach a time that MySQL will silently truncate:
ImprovedDate::make('Due Date', 'due_date')
->format('DD/MM/YYYY')
->pickerFormat('d/m/Y')
->rules('nullable', 'date_format:Y/m/d'),
Verify the date_format rule matches what Nova actually submits post-picker, not what the user typed.
Nova's base DateTime ships ->timezone()-style helpers for displaying in a user's tz while storing UTC. Before relying on it, check the vendored class — forks of this nature often patch or drop that behaviour. The safe pattern is to keep the model casting timezones and let the field be a pure presentation layer:
// app/Models/Event.php
protected $casts = ['starts_at' => 'datetime'];
If the field does expose a timezone setter, apply it consistently in forms and index, otherwise the same row shows two different times to the same user.
updateUsingWhen you need to transform the incoming value (e.g. combine a date with a default time):
ImprovedDate::make('Starts At')
->rules('required', 'date')
->updateUsing(fn ($value) => Carbon::parse($value)->startOfHour())
->resolveUsing(fn ($value) => $value?->format('d/m/Y H:i'));
resolveUsing lets you keep the field's display contract stable even if the underlying column format changes.
Nova lets you trim per-view:
ImprovedDate::make('Archived At')
->hideFromIndex() // keep index uncluttered
->sortable()
->exceptOnForms(); // detail only
Combined with ->onlyOnDetail() / ->onlyOnForms(), one field class covers every view without duplicate definitions.
If you write a Nova filter comparing dates, instantiate the same field class or at least reuse the pickerFormat constant — otherwise the filter UI's date picker produces 2024-01-31 while your field produces 31/01/2024, and nothing matches.
public function apply(Request $request, $query, $value)
{
return $query->whereDate('starts_at', Carbon::parse($value));
}
Always Carbon::parse() user input in the filter; never compare raw strings.
The repo is gitlab.com/jacobfricke/nova-configurable-date-field but the package is published as gabelbart/nova-improved-date-field with zero dependents. Treat it as effectively unmaintained: pin an exact version in composer.json, and be prepared to vendor the class into app/Nova/Fields/ if a Nova major bumps the Vue component contract. Before upgrading Nova, diff the field class against Nova's own DateTime — most breakage is a renamed field.* prop.
Almost always unpublished assets or a stale build. Fix in order:
php artisan nova:publish
php artisan view:clear && php artisan config:clear
npm run dev # if the package ships source
Check the browser console for a Vue "unknown component" warning — that means the JS never loaded, not that your PHP is wrong.
null saved when the user picks a valid dateFormat mismatch between the picker and the server. The submitted string is parsed by Laravel's date rule / Carbon, and 31/01/2024 is ambiguous or invalid depending on locale. Log the raw $request->all() on the failing submit — if the value is there but the model attribute is null, it's a cast/format problem, not a picker problem.
format() silently ignored on indexSome Nova versions render DateTime fields via the resolved value's own __toString, bypassing the field's format. If the index ignores your format, force it:
->displayUsing(fn ($value) => $value?->format('d/m/Y H:i'))
If the column is DATE, passing a full datetime string to MySQL 5.7+ strict mode throws. Either switch the column to DATETIME or ensure pickerFormat really has no H/i tokens. Don't rely on the field to guess.
->sortable() sorts by the underlying column, not the displayed string — which is correct, but users will report "wrong order" when the display format is DD/MM/YYYY (looks like day-first but sorts by ISO). Either sort on the real column and accept it, or provide a tooltip/label clarifying the format.
'date' accepts most formats, but 'date_format:d/m/Y H:i' is exact and unforgiving — a trailing space or a stray seconds segment fails. Debug by dumping the raw input in a FormRequest before the rule fires. For flexible input, use 'date' plus a mutator that normalises:
public function setStartsAtAttribute($value) {
$this->attributes['starts_at'] = $value ? Carbon::parse($value) : null;
}
->resolveUsing() / ->fillUsing() / ->displayUsing() — escape hatches when a fork method is missing; you rarely need to patch the package.minDate, locale) via ->withMeta([...]) and read it in the component, or by extending the field class with a small setter.->default() — pair with now() carefully; a default of Carbon::now() on a date field will include a time the column can't hold.Add one row, then check index, detail, and the edit form. The field resolves values through different paths in each (index may use displayUsing, detail may use format, the form uses pickerFormat). Most "it works but..." reports are one of these three diverging, and this field's fork status means upstream won't catch it for you.
How can I help you explore Laravel packages today?