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

Nova Time Field Laravel Package

laraning/nova-time-field

A Laravel Nova custom field that adds a clean time picker to your resources, letting you store and edit time-only values with ease. Simple to install and configure, it integrates seamlessly with Nova forms, detail, and index views.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Pin the version and check Nova compatibility first.

composer require laraning/nova-time-field:^1.2

Pin to ^1.2 — the last release was 2020-11-29, so dev-master is not a trade you want. Before installing, open the package's composer.json and compare its laravel/nova constraint to your Nova major version. This targets the Nova 3-era field API (Vue 2 component, prebuilt dist/js/field.js). On Nova 4/5, expect Composer to block the install, or the field to register but render blank — treat that as a "vendor and port" signal, not a dead end.

2. Read src/Time.php before the README. The docs are thin; the source is the documentation. It's a thin Laravel\Nova\Fields\Field subclass, so the entire public API — display format, seconds toggle, nullable — is readable in a few minutes. Note whether it ships a service provider calling Nova::script()/Nova::style(); if so, package discovery handles asset registration and you only need to use the class in a resource.

3. First use case: a shift-planning resource. Drop it next to fields you already have:

use Laraning\NovaTimeField\Time;

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Employee'),
        Date::make('Shift Date'),
        Time::make('Starts At'),
        Time::make('Ends At'),
    ];
}

4. Smoke-test the round trip immediately. Create a record, save, edit, and inspect the raw column value. This five-minute check is the highest-value thing you'll do with this package — most problems are storage-format problems masquerading as UI bugs.

5. Choose the use case consciously. Right for appointment slots, shift planning, and simple time-of-day logging. Wrong for timezone-aware scheduling, recurring events, or duration/range math. If you need any of those, stop and reach for something else.

Implementation Patterns

Form + detail display with validation

The default component renders a flatpickr picker in forms and a formatted string on detail views. For most internal tools this is the whole integration:

Time::make('Opens At', 'opens_at')
    ->rules('nullable', 'date_format:H:i')
    ->sortable(),

Field nullable() controls the UI empty state; rules() controls validation. You need both — see Gotchas.

Keep display format and stored format separate

Display format (12/24-hour, seconds shown) affects only what the user sees; the column type decides what persists. This tag typically chains format methods like:

Time::make('Opens At')
    ->format('H:i')     // display format — verify exact method name in src/Time.php
    ->withSeconds()     // picker precision — verify exact method name
    ->nullable(),

Confirm method names against the installed tag before shipping; they differ across field packages and the README won't save you here.

Nullable / optional times done properly

Time::make('Closes At')
    ->nullable()
    ->rules('nullable', 'date_format:H:i'),

A field marked nullable() will happily submit an empty string to a NOT NULL column and blow up at save. The rules are the actual contract.

Pairing with a Date field

For "date + time" scheduling persisted as one timestamp, keep the attributes separate in the database and compose in the model rather than relying on the field to emit a datetime:

// Model
protected $fillable = ['shift_date', 'starts_at'];

public function getStartsAtAttribute($value): ?Carbon
{
    if (! $value) {
        return null;
    }
    return Carbon::parse($this->shift_date->toDateString().' '.$value);
}

The field does exactly one thing (pick a time of day); date composition and timezone logic live in code you can test.

Index view rendering

->sortable() works because the underlying value is a plain string — the database sorts it. On index and detail, Nova renders the formatted string, not a picker, so the display format is the only thing that matters there. If you want a different representation in the index:

Time::make('Opens At')->displayUsing(fn ($value) => $value ? substr($value, 0, 5) : '—'),

Defaults and dependent fields

Time::make('Logged At')->default(now()->format('H:i')),

Conditional visibility via ->dependsOn(...) is exactly the path the v1.2 handleChange override touches. If you use it, explicitly test that changing a parent field re-evaluates this field's visibility — the override does not match Nova's method signature.

Custom component — the real extension point

When you need a range picker, a 15-minute step, or a custom mask, keep the small PHP field class and swap only the front end:

Time::make('Opens At')->component('my-time-field'),

This is also the first step toward full vendoring, the recommended long-term move for anything on a Nova upgrade roadmap: copy the PHP field and Vue component into app/Nova/Fields/ or nova-components/, rebuild against your own toolchain, and restore a standard handleChange. A few hours of work; it removes the single largest ongoing risk.

One test that pays for itself

public function test_time_field_round_trips(): void
{
    $slot = ShiftSlot::factory()->create(['opens_at' => '09:30']);

    $this->assertSame('09:30:00', $slot->fresh()->getRawOriginal('opens_at'));
}

That single assertion is your early-warning system for every Nova upgrade and every format change.

Gotchas and Tips

Nova version compatibility is the number-one risk. The compiled bundle in dist/ is a pre-Vite, Vue 2 artifact with no reproducible build path for you. Symptoms of a mismatch: the field registers but renders blank, a console error about an unknown component, or a 404 on the compiled JS. Check the browser network tab first — a missing dist asset is far more common than a logic bug.

The v1.2 handleChange override is a known deviation. The maintainer overrode Nova's handleChange to fix JS exception #51 and explicitly noted that it does not match the Nova method signature and would be optimised "later." Practical implications: (a) features riding Nova's change-handling pipeline — dependsOn, reactive hiding, readonly toggles — may behave oddly; (b) re-verify this path on every Nova upgrade; (c) when you vendor, replace it with the correct signature rather than carrying the workaround forward.

Treat dist/ as the shipped interface, not src/. v1.2 moved flatpickr method loading from the Vue child component up to the parent — a structural change to initialisation. If you fork or patch the Vue source, confirm the bundle in dist/ actually corresponds to the tag you installed, otherwise you're editing code that never runs. Smoke-test existing forms after upgrading even though the release is bugfix-only and declares no breaking changes.

Timezone drift. The picker hands you a wall-clock string with no timezone. If you cast the attribute to Carbon in the model, Laravel interprets that string in app.timezone and can shift it hours away from what the user typed — the classic "report is off by one hour" bug. Safest: store as a string or TIME column and convert only at the boundary where you genuinely need a datetime.

Empty string vs. null. A cleared picker may submit '', not null. Usually fine in a nullable column, but '' fails date_format:H:i and can break sortable() ordering. Add nullable to your rules and normalise '' to null via a mutator or prepareForValidation.

Seconds mismatch between display and storage. Show seconds with a TIME(0) column and users watch 10:15:42 silently truncate. Conversely, a TIME(6) column with an H:i picker yields 10:15:00.000000 in your queries. Pick one precision and apply it in the field, the rules, and the migration together.

12/24-hour display is cosmetic. Changing to AM/PM does not change parsing. A picker configured for AM/PM that stores 01:30 needs a human to verify it isn't 13:30 — check the raw column after a save, not the formatted output.

Debugging checklist, in order: browser console (flatpickr/component errors) → network tab (missing compiled asset) → php artisan nova:publish if the bundle belongs under public/vendor/ → composer show laraning/nova-time-field to confirm the installed version → raw database value after save → validation output. The PHP side is small enough that "read the class" is a legitimate debugging step.

There is no config surface. No config/nova-time-field.php, no migrations, no publishable settings. If you find yourself looking for one, stop — all behaviour is configured per-field in the resource.

Support reality check. 61 stars, zero dependents, no troubleshooting corpus. Issue searches will come up empty. Budget an hour to read the whole package once so that any future failure is a fix, not an investigation. If time-based features are business-critical or a Nova major upgrade is on the roadmap, vendor it — the code volume is small enough that owning it is cheaper than the ongoing compatibility risk.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log