idez/nova-checkboxes-field
A Laravel Nova field package that adds a checkbox group input to resource forms, letting users select and store multiple options at once. Drop-in checkboxes field with native Nova styling and minimal setup for multi-select values.
Architecture fit. This is a single Nova field: one PHP Field class plus precompiled Vue assets (dist/js/field.js), registered by a service provider with no config, no contracts, no events. It slots into a resource's fields() array like any first-party field and persists to one JSON column on the model. That is the entire architectural surface — there is nothing to integrate with beyond Nova itself, and nothing to extend. For a TPM this reads as a narrow, low-blast-radius dependency: it can only affect the forms where it is explicitly used, and it fails visibly (blank field) rather than subtly corrupting unrelated code.
Integration feasibility. Genuinely a half-day of work in a codebase already running Nova: composer require, a json/text column migration, an array cast, and a field definition. The two things that turn that into a day or more are (a) Nova version compatibility, and (b) confirming what the grouped mode actually stores. Neither is verifiable from the package metadata alone — both require a spike.
Technical risk. The dominant risk is abandonment, not code quality. Last release June 2022, five stars, zero dependents, single maintainer, no test suite in the repo. That predates Nova 4.30+ and all of Nova 5; Nova 4+ moved to Vue 3, so a Vue 2-compiled field installs cleanly and renders a blank cell at runtime. Secondary risks worth naming to stakeholders:
/nova-api payload can persist arbitrary keys, including group labels. Authorization and allow-listing are entirely the consuming team's responsibility.searchable() produces false-positive LIKE matches against the JSON blob, whereJsonContains is dialect-specific (no SQLite), and integer keys round-trip as strings.Key questions to resolve before committing:
composer.json's laravel/nova constraint and package.json's Vue version, then actually load the form.)[], null, or ''?BooleanGroup or Select::multiple() cover it? (Grouped mode is the only capability this package adds over the built-ins; BooleanGroup cannot group.)Stack fit. Assumes Laravel + Nova with a relational database. Best fit is MySQL 5.7+/Postgres, because filtering a JSON array with whereJsonContains is clean there and unsupported on SQLite — which matters for local dev and CI, and argues for a MySQL-backed test environment from day one.
Migration path. Add a nullable json column and cast it as array. Keep it nullable; do not enforce NOT NULL, because the empty-selection edge case (unchecked everything submitting null or '') is a known failure mode and a hard constraint will surface it as a 500 rather than a soft default. Backfill nothing unless the field replaces an existing column; if it does, write the migration to normalize legacy values into a consistent array shape before the field goes live, since IndexField.vue may call .join() on null and render a broken cell for pre-existing records.
Compatibility. Treat Nova compatibility as unverified until proven. The sequencing that de-risks this:
/nova-api request and response payloads in the network tab. This single exercise answers questions 2–4 above and costs an hour.FormField.vue for how field.value is read and what $emit('input', ...) sends. That determines the column type, the cast, and the accessor — do it before writing the migration.putJson to the Nova API asserting the exact persisted array catches most failure modes including group-label leakage. Assert the array contents, not assertOk().fields() and validate the flattened leaf-key allow-list server-side in a FormRequest before it renders anywhere real.The realistic long-term path is a fork. MIT license, no extension points, dormant upstream. Copying src/ and resources/js/ into nova-components/checkboxes/ and building locally is a legitimate and likely-necessary step — for Nova 5 support, for a "select all" toggle, for a max-selection limit, for a Vue 3 rebuild. Budget the fork as part of the adoption decision. If the fork isn't budgeted, the honest recommendation is to use Nova's built-ins and accept a flat, ungrouped checklist.
Alternative considered and rejected on sequencing grounds: native BooleanGroup (stores {"key": true}, renders toggle pills) or Select::make()->multiple(). Both are zero-maintenance and cover most multi-select needs. They lose only on the stacked, grouped checkbox-list affordance. If the product requirement is really "categorize these options into headings," a flat field with sorted options gets 90% of the way there.
Maintenance. Effectively zero from upstream and non-zero for you. The maintainer shipped grouped checkboxes as the 1.1.0 headline in June 2022 and nothing since. Plan for the package as a vendored internal component with your name on it, not as a dependency receiving fixes. That means: pin the version, record the fork point in the repo, and assign ownership for Nova upgrades — otherwise your next Nova major becomes an unplanned scramble across every resource using this field.
Support. Community only. No SLA, no security response process, no issue triage expectation. For a form control in back-office tooling that is acceptable; it would not be for anything customer-facing or revenue-path. Note that the absence of upstream activity also means an absence of upstream regressions — the code you audit is the code you keep.
Scaling. Runtime cost is negligible — it is a form control, not a query path. The scaling concern is data-model, not throughput: every JSON-column field accumulates ad-hoc filters, search workarounds, and denormalized helper columns as reporting requirements land. Six months in, expect to own a channels_text column or a model scope to make the values queryable. If the attribute is likely to become a first-class entity (permissions, tags with metadata, anything that needs joins or counts), don't store it as JSON at all — model it relationally and use this package only as the form affordance via fillUsing/resolveUsing, or replace it with a native BelongsToMany field.
Failure modes to instrument for:
null/'' instead of [], breaking a non-nullable column or a foreach in an accessor. Guard both the fill and the resolve side; resolveUsing(fn ($v) => $v ?? []) prevents the create-form render loop from choking.v-model on a nested object persists 'Content' alongside 'posts.create'. Higher risk in grouped mode; caught only by asserting exact array contents.1 becomes "1"; whereJsonContains('channels', 1) won't match. Standardize on string keys everywhere..join(). Mitigate with displayUsing() or hideFromIndex().Ramp-up. Low for anyone who has built a Nova field before — a few hours to be productive, a day to be confident. The flat API deliberately mirrors Select::options(), so it is learnable by analogy. The grouped API is the exception: it is the newest, least-documented, least-battle-tested surface in the package, and it is simultaneously the only reason to choose this package over free built-ins. Concentrate review effort and test coverage there, and treat the README's grouped example as a starting hypothesis rather than a specification.
How can I help you explore Laravel packages today?