inspheric/nova-defaultable
Nova Defaultable lets Laravel Nova fields and resource forms start with sensible prefilled values, reducing repetitive data entry. Configure defaults once and streamline create workflows across your Nova admin panels.
Architecture fit. Nova Defaultable is a single trait plus a service provider that attaches a default() (and, from v1.2, defaultLast()) macro-like behaviour to Nova field definitions. It occupies the narrow seam between "field is rendered" and "field is submitted," which is exactly where create-form prefill belongs. That is a good architectural fit for a Nova-based admin surface: it introduces no new persistence layer, no new data model, and no runtime service to operate. Defaults remain declarative statements living beside the field they belong to, which keeps resource classes readable and keeps the business rule visible to whoever reviews the resource.
The fit degrades the moment defaults need to apply outside the Nova request lifecycle. The package is a display/request-time convenience, not a domain-level defaulting mechanism. Any record created by a seeder, import, API endpoint, queue job, or console command bypasses it entirely. A TPM should treat this as a UI-ergonomics tool with a defined blast radius — the Nova create form and the resource action modal — and require that real defaults also live in migrations, $attributes, or a model observer wherever non-Nova creation paths exist.
The v1.2 addition of action-modal support is the architecturally interesting part, and the reason this package is worth evaluating rather than dismissing outright. It lets one mechanism govern both "new record" and "run this action" flows, which prevents the classic divergence where the create form starts records in draft but the "Duplicate as draft" action starts them in null. That said, whether action support actually fires is version-dependent, and the release notes themselves flag defaultLast() as carrying a caveat/workaround — a signal that the fallback ordering is not something to build a workflow on without proof.
Integration feasibility. Low effort, low complexity, non-trivial uncertainty. Installation is a Composer require plus trait usage; the entire package is short enough for an engineer to read end to end in under an hour, which is the single best de-risking action available. Two feasibility gates must be cleared before any commitment:
laravel/nova to older majors and refuses to resolve on a modern app. The correct remedy is a forked or path repository with a widened constraint — not --ignore-platform-reqs in CI, which suppresses the signal that would tell you the package is genuinely incompatible.Technical risk. The headline risk is dependency risk, not technical risk. Fifty-one stars, zero dependents, and a last release of 2019-05-29 describe a package with no maintenance guarantee, no security-patching commitment, and no predictable cadence. A single v1.2 revival release is evidence that someone touched the repository, not evidence of ongoing stewardship. The mitigating factor is symmetric: the implementation is small, MIT-licensed, and fully vendorable, so the exit cost is near zero. This is a rare case where an unmaintained dependency is defensible precisely because replacing it is trivial.
Secondary risks, all manageable but all capable of producing silent wrongness rather than loud failure:
^1.0, default() inside an action's fields() does nothing at all, and the team debugs the wrong layer for an afternoon.default()/defaultLast() compete with withMeta(['value' => ...]), resolveUsing(), and fillUsing(). Stacking them produces a field whose behaviour nobody can predict from reading the resource class.hideWhenCreating() or canSee => false is display-only and never reaches the request — a default that looks correct in code and does nothing in production.$request->user()->id becomes a null-pointer or a silently empty assignment.draft where they expected published.Key questions.
defaults() helper, a model observer, or the database? Who owns it when create-form and action defaults disagree?defaultLast()? If so, has precedence been proven by a test rather than inferred from the method name?->withMeta(['value' => ...]) plus model attributes delivers the same outcome with zero dependency.Stack fit. Fits a Laravel + Nova back-office application, and fits it best where Nova is the primary creation path for the affected records — internal admin tools, ops consoles, support dashboards. It fits poorly where Nova is one of several clients over the same domain: if a mobile app, a public API, and an artisan import all create the same entity, then field-level defaults in a Nova resource encode a business rule in the least authoritative place in the system. In those environments, adopt the pattern for UI polish and put the durable rule in the model or schema.
The natural landing zone is a small, well-bounded set of high-traffic resources where operator input is repetitive: status = draft, owner = current user, publish_at = now + 1 day, tenant or team assignment. These are the cases where defaults demonstrably shorten time-to-create and reduce entry errors, and where the package's limitations are least likely to matter.
Migration path. There is no migration — the package adds no tables, no columns, and no state. That is a genuine advantage for sequencing: adoption and reversal are both code-only changes, and neither requires a deployment window beyond a normal release. The one migration-adjacent obligation is audit: before adopting field defaults, confirm the underlying columns already carry sensible database defaults or $attributes, because the package will not help records created by anything other than Nova. If the schema lacks those defaults, adding them is the higher-value piece of work and should be sequenced first.
Compatibility. Treat compatibility as a two-axis problem: package-to-Nova and package-to-our-fields. The first is a Composer resolution question answered by attempting the install and, if it fails, forking with a widened constraint. The second is a semantic question answered only by reading vendor/inspheric/nova-defaultable-fields/src and confirming the exact method names and precedence our installed version exposes. Documentation drift is expected here; the trait source, not the README, is the contract.
A specific compatibility trap: defaultLast() is a fallback, not a second default. Its purpose is to supply a value only when nothing else provided one, and the release notes flag a caveat/workaround around its ordering. If a workflow depends on it, the ordering must be asserted in a test rather than assumed. The safe convention is one defaulting call per field, enforced in code review, with defaultLast() reserved for genuinely optional values and treated as an inspected pattern rather than depended-on behaviour.
Sequencing. Recommended order, each step cheap and each step gating the next:
Select, Boolean, Text). No closures, no relations. Verify create prefills and edit does not. This is the contract test.owner = current user) and confirm guarded context assumptions hold. Then add a BelongsTo relation default paired with hideWhenCreating().app/Nova/Concerns/Defaultable.php with identical call sites, or replace it with withMeta(['value' => ...]) plus model attributes. Doing this at pilot scale costs a day; doing it after five resources depend on the package costs a sprint and a regression risk.defaults() helper on each resource, so create-form and action defaults read from one array rather than two hand-maintained copies. Silent divergence between those two surfaces is the failure mode this step exists to prevent.Two decisions should be made explicitly at step 2 and recorded: whether the installed version is v1.2 or later (pin ^1.2 — older constraints silently skip action defaults), and whether any field will carry both default() and defaultLast(). The answer to the second should be no unless a test proves the precedence.
Maintenance. The package carries near-zero ongoing operational burden and a meaningful ongoing ownership burden. There is no service to monitor, no queue, no scheduled job, no external API. What we own is the dependency's fate: with no release between May 2019 and v1.2, we should assume no one is watching Nova's upgrade path on our behalf. The realistic maintenance event is a Nova major upgrade that breaks field resolution or action modality, discovered during the upgrade rather than before it.
Mitigations are cheap and worth budgeting up front: pin ^1.2 in composer.json, add a comment in the resource explaining why the trait is present, and add the vendored-trait or withMeta replacement as a documented fallback in the upgrade runbook. A TPM should also assign a named owner for the dependency — "the Nova team" is not an owner for a package with zero dependents. Realistically the effort is a few hours per Nova upgrade cycle, absorbed into the upgrade itself.
Support. No vendor support, no SLA, no security disclosure process, no issue-triage cadence. Internal support burden is low because the failure modes are visible and diagnosable, but the diagnosing requires someone who knows that $field->jsonSerialize() and $field->meta() reveal whether a default reached the payload — which distinguishes "package didn't fire" from "field was hidden or overwritten downstream." That knowledge should be written down at pilot time, not reconstructed during an incident.
The supportable position, stated plainly to stakeholders: this is an internal convenience layer with a small blast radius, owned by the team that built the admin surface, with a documented one-day exit. It is not a platform dependency and should not be described as one in planning.
Scaling. Scales along a dimension that is not the usual one. Traffic is irrelevant — the package does no I/O. The scaling axis is number of resources and fields adopting it, and the constraint there is consistency, not performance. Ten resources each defining their own inline defaults is worse than three resources reading from a shared defaults() helper. Once the pattern spreads without a convention, the organization gets divergent defaults between create forms and action modals, and nobody can answer "what status does a new record start in?" without reading every resource class.
One real performance consideration: closure defaults evaluate on every form render and action-modal open. They must be pure and fast — no N+1 queries, no remote lookups inside the closure. A closure that hits the database on every render is a latent performance problem that will only surface under administrator load. Static values for anything that does not need request context, and closures reserved for genuinely context-aware values.
Failure modes. Ranked by likelihood times damage:
default() in an action's fields() does nothing. Loud enough to catch in QA if the action is exercised, silent if it is not.hideWhenCreating() looks correct in code, does nothing in production, and leaves the column null. Confusing precisely because the code reads as intended behaviour.default() stacked with withMeta(['value' => ...]), resolveUsing(), or fillUsing() — last writer wins, and which writer is last is not obvious from the resource file. Prevented by convention, not by tooling.optional($request->user()) or a static fallback.--ignore-platform-reqs and defers the real incompatibility into runtime.The failure-mode profile is favourable overall: most failure is visible and testable, and the worst case is a one-day replacement rather than a rewrite.
Ramp-up. Very fast for anyone already fluent in Nova resource definitions, because there is nothing new to learn conceptually — the trait adds a method to a field builder. Estimate half a day for the first engineer to read the source, install, and validate create/edit/action behaviour; then near-zero for subsequent engineers, who only need the convention ("one default per field, defaults come from the resource's defaults() array, never stack with withMeta").
The ramp-up that actually needs planning is stakeholder-facing, not engineering-facing. Two things should be communicated before the work starts. First, scope: this improves Nova create forms and action modals only, and does nothing for records created by imports, APIs, or jobs — if those paths matter, database or model defaults are the deliverable and this package is decoration. Second, ownership: we are adopting an unmaintained add-on with a known, costed, one-day exit, which is an acceptable trade for internal admin ergonomics and an unacceptable one for anything customer-facing or compliance-adjacent.
Framed as a backlog item — "reduce admin entry errors and standardize record creation" — the investment case is a few days of engineering for measurable improvement in operator throughput and a reduction in malformed records. Framed as a platform initiative, the case collapses immediately. Keep it in the first category.
How can I help you explore Laravel packages today?