dnwjn/nova-button
A Laravel Nova package for adding customizable buttons to resource pages, letting you trigger links, actions, or modals directly from Nova while controlling visibility and authorization.
Architecture fit. dnwjn/nova-button is a Nova field, not a platform or service. It slots into a resource's fields() method alongside native fields and renders an actionable control in index, detail, and form views. That is a very narrow architectural claim, and it fits cleanly: no new runtime, no queue worker, no database table, no service provider choreography beyond Nova's standard field registration. The package's value is entirely UI ergonomics — surfacing a labeled, visible entry point where Nova's native Actions dropdown buries the same operation. The highest-leverage architecture is therefore button as trigger surface, Nova Action or policy-protected controller as logic. If a team starts moving validation, permission matrices, audit trails, or multi-step confirmation into the button, the requirement is mis-scoped.
Integration feasibility. High for a competent Laravel/PHP developer on Nova 5. Installation is one Composer require, one nova:publish, and a cache clear. The fluent API (make, action, url, icon, confirm, canSee) covers the common cases. Feasibility drops sharply off Nova 5: release 5.0.0 added Nova v5 support and dropped v4 in the same release, so the package is effectively a two-branch artifact — 5.x for Nova 5, a frozen 4.x for Nova 4. There is no compatibility shim and no deprecation runway.
Technical risk. The dominant risk is not code quality; it is supply chain thinness. Eleven stars, zero dependents, one maintainer, and the last release (2024-12-24) exists mainly to track a Nova major. That means nobody has absorbed the next Nova major ahead of us, and we are maintainer of last resort between the package's own upgrade releases. Second-order risks:
src/Button.php.Key questions to answer before committing.
composer.json alongside the package major?nova:publish is load-bearing in CI.)app/Nova/, or skip and use native Actions with showOnIndex/showOnDetail?Stack fit. Requires Laravel + Laravel Nova (paid) + a PHP developer who owns the admin. Nothing else. It is worthless outside Nova, so this is only a live decision if Nova is already a sunk cost on the roadmap. On Filament or a Blade/Livewire admin, the answer is "build the equivalent in an afternoon," not "adopt this."
Migration path. There is no migration in the data sense — no tables, no backfill. The migration is organizational: moving an operation from "buried in the Actions dropdown" or "done by hand in tinker/DB" to a labeled control on the resource screen. That is a one-day change per workflow, which is exactly the framing to use in planning: a one-day internal build with a head start, not a procurement.
Compatibility. Hard-gated on Nova major. Pin explicitly — composer require dnwjn/nova-button:^5.0 for Nova 5 — and record the Nova-major/package-major pairing in the repo (a line in the README or a comment in composer.json) so the next engineer does not discover the coupling in production. No floating constraints. On Nova 4 or earlier, read 5.0.0 as end-of-line for that branch: vendor the component rather than pin to a frozen 4.x line.
Sequencing. Recommended order:
src/Button.php, then the Vue component. Answer the dispatch-path question above. This gates everything else.canSee closures to hide buttons on records the operator cannot act on, confirm() on anything mutating.If a Nova major upgrade is already penciled into the roadmap, fold this package's upgrade into that line item rather than tracking it separately: it is one PR, one rollback point.
Maintenance. Low in steady state, spiky at Nova releases. There is no server footprint to operate — no queue, no cron, no storage. The recurring cost is the human cost of being maintainer of last resort: reading source on each Nova major, republishing assets, and running one manual smoke test per Nova minor upgrade (render an index, click a button, confirm the action fired and authorization held). Budget that as a recurring line, not a one-time install. Keep a vendoring escape hatch pre-approved: the component is small and MIT-licensed, so copying it into app/Nova/ and owning it is a realistic, one-day decision — the honest framing is "depend vs. copy," not "buy vs. build."
Support. Effectively none from upstream. One maintainer, porting on their own schedule after Nova ships, zero dependents to absorb a break ahead of us. Internal support is whoever owns the admin. Documentation coverage is thin at this adoption level; the source is the spec. That is acceptable for a UI field and unacceptable for anything on a customer-facing path.
Scaling. Not a scaling concern in the conventional sense — the package adds a render-time field, not a workload. The real scaling constraint is change-site scaling: every resource that hand-rolls a button is a place a future major bump must be fixed. Centralizing into a trait or base resource is what keeps this linear instead of combinatorial. The second scaling constraint is organizational: if the roadmap assumes a rich third-party Nova plugin ecosystem to lean on, this package is the counter-evidence — with Nova, "roll your own custom field" is the norm and that labor is recurring.
Failure modes and detection.
| Failure | Symptom | First check |
|---|---|---|
| Stale/missing assets | Button blank or unstyled | php artisan nova:publish, clear caches, check console for 404 on JS asset |
| Field not on view | Button doesn't appear | Is it in fields() for that view; does it need explicit showOnIndex/showOnDetail |
canSee too strict |
Button hidden for valid users | Test with a permissive closure |
| Action not autoloadable | Click does nothing | Namespace/autoload; add a log line in handle() |
| Dispatch path unhardened | Action fires without policy check | Watch network tab; confirm route is Nova's action endpoint |
| Silent long-running action | Ten seconds of nothing, reads as broken | Wire queued actions into Nova's progress UI |
| Nova major drift | Field breaks after Nova upgrade | Lockstep pin; one smoke test per minor |
The two failure modes that matter are the ones with real blast radius: authorization gaps on the dispatch path, and unconfirmed non-idempotent mutations. Both are design-time mitigations, not operational ones — canSee for UX, policy for truth, confirm() for anything destructive.
Ramp-up. Roughly half a day for a Laravel developer to be productive: publish, read the field class, ship the detail-view smoke test. Full confidence — knowing which fluent methods exist and where the click actually lands — takes a focused source read, because the README will not carry it. Team-level ramp cost is mostly policy education: everyone touching the admin needs the same two rules — pin Nova and package majors together, and keep business logic in Actions and controllers, never in the button.
Bottom line for planning. Adopt pinned and time-boxed, internal-only, non-destructive actions first, with a source-verified authorization path before anything touches money or records. Treat it as code we may have to own, and fold its upgrade cost into the next Nova major line item.
How can I help you explore Laravel packages today?