oneduo/nova-time-field
Adds a dedicated Time field to Laravel Nova, giving you a clean, configurable time picker for resource forms and detail views. Easy to install and customize for any Nova project.
Architecture fit. This is a thin presentational dependency: a Nova Field subclass plus a Vue component that renders inside the Nova SPA. It introduces no routes, no domain services, no background work, and no persistence layer of its own — it captures a value and hands it to Nova's existing resolve/fill cycle. That makes it a clean fit for the architecture we actually want: a naive wall-clock TIME column stored as HH:mm:ss, with timezone, recurrence, duration, and conflict logic living in application code where it can be tested and reasoned about. Critically, the package does not solve scheduling; adopting it must not be read internally as "time handling is done." Scope the decision to "we are buying a form control."
Integration feasibility. High, with one gate. The mechanical work is composer require, a use statement in a resource, and Time::make('opens_at', 'opens_at')->nullable(). Realistic estimate: one hour of source audit (read the field class and the Vue component; confirm the exact namespace in vendor/ rather than guessing from the README) plus roughly half a day for the first field including migrations, validation rules, and a round-trip smoke test. Every field after that is minutes. The gate is compatibility: open the installed package's composer.json and check its require block against our actual Nova major, laravel/framework, and PHP version — the README is a claim, the installed constraint is the truth. Also confirm on day one whether compiled JS assets ship with the package or whether it expects a Node build pinned to Nova's tooling; that answer determines the entire upgrade risk profile.
Technical risk. The popularity signal is weak — 13 stars, zero dependents, single maintainer — and that is the honest headline. It is offset by three things that matter more than stars for a dependency of this shape: MIT license (fork is legally trivial), a surface area small enough to audit in an afternoon and reimplement in roughly half a day, and a recent release (2026-05-06) suggesting the maintainer is still tracking Nova majors. The dominant failure vector is not abandonment — it is a Nova major upgrade breaking the compiled Vue/Tailwind assets, which produces a blank or unstyled widget rather than a failing test. Secondary risks: mixed storage formats (H:i rows next to H:i:s rows) silently breaking lexicographic sorting and whereBetween; midnight (00:00:00) being falsy in PHP and disappearing from naive "is configured" checks; a future Nova shipping a native Time class and colliding with an unqualified import; and a lossy, DST-ambiguous backfill if we are converting legacy UTC datetimes to local wall-clock.
Key questions to answer before merge.
composer.json actually constrain, and does it match our matrix?null/clear — does clearing persist NULL or 00:00:00?Stack fit. Laravel + Nova + Vue, admin surface only. It requires one engineer who is fluent enough in Laravel and Vue to audit a small Nova field and, if upstream goes quiet, fork it. We have that; if we did not, this decision would flip toward in-house code or blocking. It has no bearing on the customer-facing stack and should be treated as strictly insulated from it.
Migration path. Two distinct paths depending on whether we are adding a new attribute or replacing one:
time column, wire the field, add an explicit validation rule matching the storage contract (['nullable', 'date_format:H:i:s']), smoke-test the round trip including the clear.time column, backfill with an explicitly reviewed transform, keep the legacy field read-only for one release, then drop it. The backfill is the risk, not the field. If legacy data is a UTC datetime, converting to local wall-clock is lossy and DST-ambiguous — write that policy down before the backfill script exists, and get product sign-off on the DST edge-case behavior rather than discovering it in production.Reversal cost is asymmetric and that is the point: swapping the field out is a one-line change and the data stays put. Abandoning the naive-TIME storage decision, by contrast, is a data migration. Make and record that schema commitment explicitly — it matters more than the package choice.
Compatibility. Verify against the installed vendor tree and the issue tracker, not the README. Test against our 12/24-hour display locale and our null-handling requirement before declaring the pilot successful. Alias the import defensively in any resource that might later import a native Nova Time, and revisit that at every Nova upgrade.
Sequencing.
vendor/ — field class, Vue component, asset shipping, resolve/fill, exact namespace.opens_at, cutoff_at, shift_start. Not something that is secretly an instant.14:45:00, confirm DB value, reload detail view, edit, clear, confirm NULL not 00:00:00, confirm a fixed wall-clock value survives a DST transition date unchanged.after_or_equal) at the request level — the field has no notion of "close must be after open."Maintenance. This dependency requires a named owner — that is the non-negotiable condition of adoption. Ongoing cost is near zero in steady state, but it spikes at every Nova upgrade, which is why pinning, mirroring, and a documented fork path belong in the acceptance criteria rather than in a follow-up ticket. Budget the fork/reimplement option at roughly half a day of engineering; that number is what makes the single-maintainer risk tolerable rather than merely noted.
Support. There is no SLA, no vendor relationship, and community-only issue response. That is acceptable only because the blast radius is confined to internal admin forms. If a requirement ever pushes this widget onto a customer-facing or revenue-critical surface, the support posture alone disqualifies it — revisit with an in-house build or a maintained alternative.
Scaling. Runtime load is a non-issue: admin-panel QPS is trivial and the package does nothing server-side at request time. The real scaling axis is breadth of usage. Each additional field is cheap to add but permanently enlarges the surface that must be re-verified on every Nova upgrade. Keep the storage-format contract (H:i:s, machine-readable, never g:i A) consistent project-wide — mixed formats are how sortability and range queries break quietly six months in. Prefer displayUsing(fn ($v) => $v ? substr($v, 0, 5) : '—') for compact index columns rather than changing what is stored.
Failure modes to plan for.
00:00:00 instead of NULL. Corrupts "is configured" logic. Mitigation: assertion in the pilot and in any test suite touching these columns; never use empty() on a time value — '00:00:00' is falsy.H:i and H:i:s rows coexist; sorting and whereBetween return wrong results. Mitigation: single project-wide validation rule.Time. Presents as a rendering bug, not a type error. Mitigation: defensive aliasing.Ramp-up. About an hour to audit, half a day for the first integration including schema and validation, minutes per field thereafter. No new framework knowledge is required beyond the Nova field API the team already uses — which is precisely why this is a defensible buy over build: the alternative is one to three engineer-days on picker UX, 12/24-hour formatting, seconds/step handling, null behavior, styling, and tests, all to arrive at roughly the same primitive.
Recommendation. Approve a bounded pilot: pin the version, wire one low-traffic nullable time column, prove the round trip including clear-to-NULL, assign a named owner, mirror the repo, and add the field to the Nova upgrade checklist. Revisit at the next Nova major and retire the dependency the moment a native equivalent ships.
How can I help you explore Laravel packages today?