Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Multiselect Checkboxes Laravel Package

mohkoma/nova-multiselect-checkboxes

A Laravel Nova field for selecting multiple options with checkboxes, offering a clean, searchable, and customizable way to manage many-to-many relationships and long option lists inside your Nova resources and forms.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require mohkoma/nova-multiselect-checkboxes

The package is a standard Nova field: a service provider registers a compiled Vue component and the PHP field class ships under src/. There is no config file to publish. If the compiled JS doesn't appear in the browser, publish Nova's assets (php artisan nova:publish on Nova 3, or php artisan vendor:publish --tag=nova-assets) and hard-refresh.

First use case: a JSON-backed tags/roles column

// migration
$table->json('roles')->nullable();

// model
protected $casts = ['roles' => 'array'];

// Nova resource
use Mohkoma\NovaMultiselectCheckboxes\NovaMultiselectCheckboxes; // verify the exact namespace in src/

NovaMultiselectCheckboxes::make('Roles')
    ->options([
        'admin'   => 'Administrator',
        'editor'  => 'Editor',
        'viewer'  => 'Viewer',
    ])
    ->rules(['nullable', 'array'])
    ->rules(['roles.*' => 'string']),

That's the 90% case: a dropdown that opens a checkbox list, submits an array, and stores JSON.

Where to look first when something is off

  1. src/ — the field class. Confirm the namespace, the options()/resolveUsing() signatures, and whether it calls ->displayUsing() internally.
  2. resources/js/components/ — the Vue component. This tells you whether the dropdown stays open on click, how it emits values, and whether it supports dependsOn, searchable, or readonly at all.
  3. composer.json — the laravel/nova and php constraints. Released Feb 2020, so expect Nova 2/3-era constraints (and Vue 2). Treat Nova 4 as unsupported until you've verified otherwise.

Given 3 stars, zero dependents, and no release since early 2020, budget for reading the source rather than trusting docs — and plan to vendor/fork it if it's load-bearing.

Implementation Patterns

1. JSON column + array cast (default, recommended)

The field submits an array; the array cast handles encode/decode. This keeps whereJsonContains('roles', 'admin') available for filtering and reporting.

2. Comma-separated string column (legacy schemas)

If the column is a string/text, the raw array will fail to save. Adapt with a mutator/accessor or use fillUsing:

NovaMultiselectCheckboxes::make('Roles')
    ->options($options)
    ->fillUsing(function ($request, $model, $attribute) {
        $model->{$attribute} = implode(',', (array) $request->input($attribute, []));
    })
    ->resolveUsing(fn ($value) => $value ? explode(',', $value) : [])

resolveUsing is essential here — without it the component receives a string and no box is checked.

3. BelongsToMany instead of a column

Don't store the array; use the field as a UI and sync on save:

NovaMultiselectCheckboxes::make('Roles')
    ->options(Role::pluck('name', 'id'))
    ->fillUsing(fn () => null)   // don't write to the model
    ->resolveUsing(fn ($value, $model) => $model->roles->pluck('id')->all()),

Then sync in the resource's afterSave/observer, or override saved. Keep the roles attribute out of $fillable so Nova's mass-assignment doesn't blow up.

4. Options from a closure / dependent on another field

Most Nova fields evaluate options() lazily per-request, so a closure over $this->resource works for "options vary by record". Cross-field reactivity (change Country → reload States) requires the Vue component to declare dependsOn; check the component before promising it. The reliable pattern is a BelongsTo + dependsOn, or reloading options server-side on creating/updating of the dependent field.

5. Index and detail display

Raw JSON on the index table is unreadable. Wrap it:

->displayUsing(fn ($value) => collect($value ?? [])
    ->map(fn ($k) => $options[$k] ?? $k)
    ->implode(', '))

Do this for both index and detail — Nova's displayUsing applies to both unless you use onlyOnIndex/exceptOnDetail.

6. Validation that actually matches the widget

Submitted values arrive as strings. Keys in your options() array are often integers. Guard against the classic "1" !== 1 mismatch:

->rules(['nullable', 'array'])
->rules(['roles.*' => Rule::in(array_map('strval', array_keys($options)))])

7. Filtering

Ship a Nova filter that translates the checkbox group into whereJsonContains (MySQL 5.7+/8, Postgres jsonb) or LIKE for comma-separated columns. This is where a multiselect column earns its keep — without a filter, admins will ask for one within a week.

Gotchas and Tips

Nova version compatibility is the big one. A 2020 release predates Nova 4 (Vue 3). Symptoms on Nova 4: the field renders nothing, renders as a plain text input, or the dropdown is unstyled/unclickable. Verify in a staging branch before committing. If you're on Nova 4, prefer outl1ne/nova-multiselect-field or a small custom field — forking a 200-line package is usually cheaper than maintaining a Vue 2 shim.

Empty selection semantics. Depending on the component, clearing all boxes may emit [], null, or omit the key entirely. Handle all three: $request->input('roles') ?? [], and cast in the mutator with (array). Also decide whether "no selection" is allowed — nullable plus array rules avoids the "must be an array" 422 on an untouched form.

Integer option keys get stringified in JSON. If options() is Role::pluck('name', 'id'), the JSON round-trip and the request both give strings. Cast on read ($model->roles may be ["1","2"]), and don't compare with === against ints anywhere.

No searchable and no sortable. A long option list becomes a scroll-fest. Sort the options array before passing it in (collect($options)->sort()->all()) and consider ->help() to nudge admins toward the type-ahead alternative. Sorting the index column on a JSON field is DB-specific and generally not worth it.

Dropdown clipping inside panels/tabs. Absolutely-positioned dropdowns inside Nova's Panel or Tab components can be clipped by overflow: hidden, or appear behind sibling fields. If the list is cut off, the fix is usually a CSS override on the field wrapper (overflow: visible) or a z-index bump — expect to write a small nova.css in your app, not in the package.

Performance on index listings. Resolving an options map per row is fine for 10 options, terrible if you pluck() inside displayUsing for 50 rows. Compute the map once, up front, in fields():

$options = Role::pluck('name', 'id')->all();
// reuse $options in both ->options() and ->displayUsing()

fillUsing and resolveUsing must be symmetric. If you transform on save, you must inverse-transform on read, or the edit form shows nothing checked. This is the single most common bug with this class of field.

Debugging checklist, in order: (1) is the Vue component registered — check the page source for the component name in the Nova script bundle; (2) is the field receiving an array — dd($field->value) in the resource or hit the Nova detail endpoint; (3) is it a save problem or a display problem — check the DB column directly.

Extension points are thin. This package is essentially one field class and one component. If you need "select all / clear all", counts in the trigger button, or per-option descriptions, you're editing the vendored component. Do it via a fork (repositories + vcs in composer.json) rather than vendor/ patches so the change survives composer install.

Pin the version. With no releases since 2020 and no maintainer activity, an unpinned require is a supply-chain and reproducibility hazard. "mohkoma/nova-multiselect-checkboxes": "1.0.0" (or a commit hash) protects you. And if the field is used on more than a couple of resources, write it down in the README — future you will not remember why this dependency exists.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk