fourstacks/nova-checkboxes
fourstacks/nova-checkboxes adds a multiple-checkbox field to Laravel Nova, letting admins select several options for one attribute. Define option lists in PHP, render them as checkboxes, and save selected values as an array—useful for settings, tags, permissions, and multi-select forms.
Architecture fit. fourstacks/nova-checkboxes is a single-purpose Laravel Nova field: one thin PHP Field subclass (src/Checkboxes.php) plus a compiled Vue component that renders a checkbox group bound to a multi-value model attribute, persisted as a JSON array on an existing column. Its architectural blast radius is deliberately tiny — it touches only how operators tick options inside a Nova resource form and detail/index views. It has no bearing on customer-facing flows, APIs, background jobs, or the data model beyond that JSON payload. That makes it an internal-tooling decision, not a product capability decision, and it should be scoped and governed accordingly.
Integration feasibility. Mechanically trivial if and only if the Nova generation matches:
composer require fourstacks/nova-checkboxes; the service provider is auto-discovered and registers the compiled asset via Nova::script() — no vendor:publish for the field itself.json/text column plus protected $casts = ['features' => 'array'] on the model. Without the cast, saves throw "Array to string conversion" or the field silently re-renders unchecked — this is the single most common integration bug.Field subclass plus a Vue component, and we would be buying an archived 2019 snapshot.Technical risk. The dominant risk is vendor abandonment, not code quality. Last release 2019-12-17, repository archived, 91 stars, zero dependents. There is no upstream to patch a defect, no compatibility work for future Nova/Laravel/PHP releases, and no community canary — we would be the first to discover any failure. The compiled Vue 2 asset predates modern Nova asset pipelines and the Vue 2 → 3 shift, so on Nova 3+ the typical symptom is a blank field or a console error rather than a clean PHP exception — a debugging cost that lands on whoever is on call, not on the vendor. Secondary risks: no filter, sorting, or index-search support ships with the package, so any real usability requirement becomes custom work regardless; and whereJsonContains filtering is a full table scan without a functional/GIN index.
Key questions (to close before any commitment):
BooleanGroup available to us? It does the same job with the identical JSON array storage shape — meaning a native-first evaluation costs nothing and preserves a free exit.Stack fit. Constrained to Laravel Nova 2 (limited 3.x relevance) on PHP 7.x with Vue 2. If our stack is Nova 4+/Laravel 9+/PHP 8+, the package is a non-starter on asset-pipeline grounds alone, and BooleanGroup is the correct target.
Migration path (decision order matters).
| Option | When it applies | Cost | Recommendation |
|---|---|---|---|
Native BooleanGroup |
Nova 4+ | Near zero; same JSON shape, no data transformation | Default — evaluate first |
| Vendor the source | Nova 2/3 with no near-term upgrade | < 1 day including tests | Acceptable fallback |
| Composer dependency | Almost never | Low now, unbounded later | Do not adopt |
The critical planning fact is that the data shape is portable and reversible: selected keys persist as a JSON array, exactly what BooleanGroup writes. That means the storage decision is cheap and the dependency decision is not — so take the storage pattern, decline the dependency, and keep the exit open.
Compatibility. Pin laravel/nova and the package together in composer.json if we vendor it; check the laravel/nova constraint in the repo composer.json before installing anything. Use stable string keys in options() — a plain list (['Newsletter', 'Updates']) stores integers 0,1, so reordering or inserting an option silently reinterprets existing records. This is a data-integrity constraint, not a style preference, and should be enforced in code review. Centralise the option list in one shared class (e.g. app/Support/Features.php) so form, validation, and filters cannot drift.
Sequencing.
BooleanGroup availability. If native fits, stop — no package, no dependency, no further work.$casts = ['array'], string slugs as keys. This survives whichever rendering option we choose.dist/js/field.js (it is a committed build — a fork that skips the rebuild ships stale JS).php artisan nova:publish and hard-refresh; verify the field renders and round-trips a save on staging, not production.whereJsonContains, and a displayUsing()/computed Text field if index presentation needs to be richer than plain labels.Maintenance. If adopted as a dependency, maintenance is zero by the vendor and total for us — an archived repo accepts no fixes, so every defect becomes an internal ticket with no upstream answer. If forked, we own roughly a few hundred lines of PHP and Vue, plus a build step for dist/js/field.js, permanently. Neither outcome is large; both are permanent. Budget the fork at well under a day including tests; budget the ownership as an ongoing line item under internal tooling maintenance.
Support. There is no support, no security patching, no SLA, and no issue triage. This is a governance finding, not a technical one: the component executes inside our authenticated admin surface, so an unpatched defect has a privileged attack surface and no patch route. That single sentence should decide the dependency question for most organisations.
Scaling. Three limits to plan around:
whereJsonContains (MySQL 5.7+/MariaDB 10.2+/Postgres jsonb) — not LIKE, which produces substring false positives (beta matching beta_access). On SQLite, json_each or LIKE '%"key"%' with quotes included.whereJsonContains is a full scan on a large table unless backed by a functional/generated-column index (MySQL 8) or a GIN index on jsonb. Decide this before the table grows, not after.scopeSearch or a generated column — again, custom work with or without the package.Failure modes.
| Failure | Symptom | Mitigation |
|---|---|---|
| Nova version mismatch | Blank field or console error, not a PHP exception | Pin Nova + package together; verify on staging first |
Missing $casts |
"Array to string conversion" on save, or field re-renders unchecked | Cast to array; check before blaming the JS |
| Integer option keys | Reordering options silently reinterprets stored records | Stable string slugs, enforced in review |
| Empty state inconsistency | null, [], or '' depending on Nova version and nullability |
Normalise with (array) $model->features on every read |
| Stale compiled asset after fork | Broken-looking field indistinguishable from a real bug | Rebuild dist/js/field.js, nova:publish, hard-refresh |
| Asset registration missing | Empty cell in the form | Verify Nova::script() registration first |
| Thin extension points | Per-option disabled states, grouping, "select all" unsupported | Fork the .vue component; treat it as ours to maintain |
Ramp-up. Low, and bounded by reading the source rather than documentation: start at src/Checkboxes.php (field class, options()/resolve logic), then resources/js/components/FormField.vue and IndexField.vue to see exactly how the value is shaped on the wire. Because there are zero dependents and 91 stars, there will be no upstream answers when it breaks — assume the team reads the code. A competent Laravel engineer is productive in half a day to a day, including the fork, one filter, and basic tests.
TPM recommendation. Do not adopt fourstacks/nova-checkboxes as a Composer dependency. Take the storage pattern (JSON array, string slugs, array cast) and the worked example, and implement the rendering with Nova's native BooleanGroup where available. Where it is not, vendor the MIT source into our codebase under test coverage as an internal-tooling line item — never as a roadmap initiative.
How can I help you explore Laravel packages today?