devloops/time-picker
devloops/time-picker is a Laravel package that adds a time-picker form input, making it easy to include time selection in Blade views and forms. A small, focused component for apps that need users to choose a time.
This package is a thin Nova field wrapper around a JavaScript time picker. With zero stars, zero dependents, and a single recent release, treat it as unvetted code you are reviewing rather than a battle-tested dependency. Spend the first ten minutes reading the source, not the README.
1. Read these files, in this order
composer.json — check the Nova version constraint. If it allows only laravel/nova: ^4.0 and you are on Nova 5, stop and find an alternative.src/TimePicker.php (or the field class) — this is the whole API surface. Note the $component string and any public setter methods (format, withSeconds, twelveHour, etc.).resources/js/components/Field.vue — confirms what the field actually emits: a HH:mm string, a HH:mm:ss string, or a full ISO timestamp. This determines your column type and cast.dist/ — if compiled assets are committed, you do not need to run npm. If they are not, the field will render blank and you must build it or abandon the package.2. Install and register
composer require devloops/time-picker
The service provider should be auto-discovered. If it is not listed in composer.json under extra.laravel.providers, add it manually to config/app.php. Nova fields normally do not need vendor:publish unless the package ships config or uncompiled assets.
3. First use case
A start_time column on a scheduling model is the canonical fit:
use Devloops\TimePicker\TimePicker;
public function fields(Request $request)
{
return [
TimePicker::make('Start Time')
->rules('required', 'date_format:H:i'),
];
}
Migration: $table->time('start_time'); and cast it on the model as 'start_time' => 'datetime:H:i' — not plain datetime, which will inject today's date and confuse your comparisons.
4. Verify before building on it
Create a throwaway resource, save a value, and inspect the raw DB row. If the field stores "14:30" you are fine. If it stores "2025-02-19 14:30:00" or "1970-01-01 14:30:00", adjust the column type before you write any business logic around it.
Pair with a date field rather than replacing it. The field holds time only, so scheduling needs a date column alongside it:
Date::make('Date', 'scheduled_date')->rules('required', 'date'),
TimePicker::make('Start Time')->rules('required', 'date_format:H:i'),
TimePicker::make('End Time')
->rules('required', 'date_format:H:i', 'after:start_time'),
Combine into a single Carbon value in a model accessor or an Attribute cast so the rest of your app deals with one datetime:
protected function startsAt(): Attribute
{
return Attribute::make(
get: fn () => Carbon::parse($this->scheduled_date->toDateString().' '.$this->start_time),
);
}
Form-only, with a formatted index column. Time-only values often render poorly in Nova's index table, so either hide the field there or format it:
TimePicker::make('Start Time')
->hideFromIndex()
->onlyOnForms();
// or, if it renders acceptably:
TimePicker::make('Start Time')->displayUsing(fn ($value) => Carbon::parse($value)->format('g:i A')),
Defaults for "now"-ish entry. Time pickers without a default are a common source of null inserts:
TimePicker::make('Start Time')->default(now()->format('H:i'))->required(),
Format alignment is a three-way contract. Whatever the Vue component emits, the validation rule, and the DB column must agree. If the field exposes a format setter, pin it explicitly and mirror it in the rule:
TimePicker::make('Start Time')
->format('H:i') // check the source — method name varies
->rules('date_format:H:i'),
Nullable optional times. For an end-time that may be blank, add ->nullable() on the field and 'nullable' to the rules, and make the column nullable. Forgetting the field-level ->nullable() is the usual cause of "value is required" errors on an untouched form.
Reuse across resources. If more than two resources need it, wrap it:
class StartTime extends TimePicker
{
public function __construct($name = 'Start Time', $attribute = 'start_time')
{
parent::__construct($name, $attribute);
$this->rules('required', 'date_format:H:i');
}
}
This also gives you one place to swap the package out later if it goes stale.
Check whether you need it at all. Nova ships DateTime::make('...')->withTime() and ->step(15) style configuration in recent versions. For many apps, a built-in field with time enabled is a better bet than a zero-star dependency. Confirm the gap before adopting this.
Nova version drift is the top risk. With no dependents and one release, nothing proves this works on your Nova minor version. Pin it in composer.json, smoke-test on a staging copy after any Nova upgrade, and be ready to drop it. A single-file Vue component is easy to vendor into nova-components/ if the package dies.
Timezone handling lives outside the field. The picker emits wall-clock strings; Nova's timezone config and any Carbon::parse you do will apply the app timezone. For multi-timezone apps, store the wall time plus an explicit timezone column, and convert only at display edges.
datetime cast eats your time-only value. Casting a time column as datetime in Eloquent yields 1970-01-01 14:30:00 or today's date depending on configuration. Use 'start_time' => 'datetime:H:i' on access, or a custom cast that returns a Carbon anchored to a real date. Never compare raw strings across the two shapes.
Validation rules must match the exact emitted format. date_format:H:i rejects 14:30:00. If the component includes seconds, the rule must be H:i:s. Copy the format from the actual saved value, not from the label.
Assets. If dist/ is missing from the tag, you must either run npm install && npm run prod inside vendor/devloops/time-picker (fragile and lost on every composer update) or fork and commit the build. Verify the field renders in the browser before writing server code.
Index and detail views may show raw values. Custom fields render through their own component; some do not implement a detail/index variant and fall back to the raw string. Use ->hideFromIndex() / ->hideFromDetail() or ->displayUsing() rather than fighting the component.
Not searchable or sortable out of the box. Time fields rarely implement Sortable/Filterable. If users need to filter by time range, add a computed starts_at column and filter on that.
Extension points are limited. Treat the field as sealed: compose via ->rules(), ->default(), ->displayUsing(), and a custom cast rather than subclassing deeply. If you need bespoke behaviour (rounding to 15 minutes, free-text entry, range selection), writing a small custom Nova field or vendoring the component is likely less work than adapting this one.
Debugging checklist when the field misbehaves: (1) php artisan nova:check / clear Nova's cached assets; (2) confirm nova-components and vendor/devloops/time-picker/dist both exist after composer install --no-dev; (3) log $request->input('start_time') in the resource's store to see the exact payload; (4) verify the model's $casts and DB column type agree with that payload.
How can I help you explore Laravel packages today?