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 Boolean Group Field Laravel Package

stepanenko3/nova-boolean-group-field

A Laravel Nova field for grouping multiple boolean attributes into one compact control, making related true/false options easier to view and toggle in admin panels. Useful for permissions, settings, feature flags, and other grouped on/off choices.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm you actually need it. This is a Nova field, not a package with runtime services. It only earns its place if your app already runs laravel/nova and you have a back-office screen where several booleans logically belong together. If Nova isn't installed, stop — adding Nova to get one checkbox group is not a trade you want.

Install and read the vendor source before writing a line of your own code. The README is thin; the source is the documentation.

composer require stepanenko3/nova-boolean-group-field:^1.0
cat vendor/stepanenko3/nova-boolean-group-field/composer.json
ls vendor/stepanenko3/nova-boolean-group-field/src/
ls vendor/stepanenko3/nova-boolean-group-field/dist/ 2>/dev/null || echo "no prebuilt assets"

Answer four questions from the source, in this order:

  1. Namespace and class name. Copy-paste examples in blog posts are frequently wrong for small fields. Grep the class ... extends line.
  2. What it extends. Does it extend Laravel\Nova\Fields\BooleanGroup, or does it bundle its own Vue component? This determines whether the native API (options(), defaults, field resolution) is guaranteed or merely "probably compatible."
  3. What it adds over native. Look for options(), default(), selectAll(), layout props, or ->inline()-style toggles. You want to know the delta so you can decide whether the native field would do.
  4. Whether dist/ ships prebuilt assets. If there is no dist/, your host app needs a Node build step (npm install && npm run dev) or the field renders as an empty div with no console error — the worst possible failure mode.

First use case: notification preferences on a user. Non-critical, small, fixed option set, obvious to eyeball after saving. Low blast radius if it misbehaves.

Schema::table('users', function (Blueprint $table) {
    $table->json('notification_preferences')->nullable(); // jsonb on Postgres
});
protected $casts = ['notification_preferences' => 'array'];
protected $fillable = ['notification_preferences'];

Register it on the resource, mirroring native BooleanGroup's API:

use Stepanenko3\NovaBooleanGroupField\BooleanGroup; // verify against vendor/src

BooleanGroup::make('Notification Preferences')
    ->options([
        'product_updates' => 'Product updates',
        'security_alerts' => 'Security alerts',
        'weekly_digest'  => 'Weekly digest',
        'beta_invites'   => 'Beta invites',
    ])
    ->help('Choose which emails this user receives.');

Smoke test the round trip before anything else. Create a record, check two boxes, save, reopen, verify. Then submit with nothing checked and verify again — that path is where nullable-vs-all-false bugs surface.

Implementation Patterns

Wrap the field in one internal factory. Never reference the vendor class from dozens of resources. One file means a fork, a revert to native BooleanGroup, or a Nova-major fix is a single-line change:

// app/Nova/Fields/PreferenceGroup.php
namespace App\Nova\Fields;

use Stepanenko3\NovaBooleanGroupField\BooleanGroup;

class PreferenceGroup
{
    public static function make(string $name, ?string $attribute = null): BooleanGroup
    {
        return BooleanGroup::make($name, $attribute)
            ->options(config('preferences.options'))
            ->rules(['nullable', 'array']);
    }
}

Define the option keys exactly once. Config file or backed enum — one source feeding the field, the validator, and every read-side helper. Form/validator drift is the #1 bug in grouped-boolean implementations, and it silently persists bad data rather than throwing.

// config/preferences.php
return ['options' => [
    'product_updates' => 'Product updates',
    'security_alerts' => 'Security alerts',
    'weekly_digest'   => 'Weekly digest',
]];

Normalize on read, never on write. Merge stored values over a full defaults map so adding a fifth option needs no backfill migration — old rows fall back to false. This is the single highest-leverage habit with JSON-column fields, and it's also what makes rollback to native Nova survivable.

public function getNotificationPreferencesAttribute($value): array
{
    return array_merge(
        array_fill_keys(array_keys(config('preferences.options')), false),
        json_decode($value ?? '[]', true) ?: []
    );
}

Validate the keys, not just the shape. A JSON attribute will happily persist {"typo_key": true} and you'll discover it months later:

->rules(['nullable', 'array', function ($attribute, $value, $fail) {
    $unknown = array_diff(array_keys($value ?? []), array_keys(config('preferences.options')));
    if ($unknown) {
        $fail('Unknown preference key: ' . implode(', ', $unknown));
    }
}])

Keep raw JSON out of index views. A JSON blob in a table column is noise. Either hide it and expose a readable summary, or render one:

->hideFromIndex()
->displayUsing(fn ($value) => collect($value ?? [])
    ->filter()
    ->keys()
    ->map(fn ($k) => config("preferences.options.$k"))
    ->implode(', ') ?: '—')

Shape flags explicitly in API resources. Don't return $user->notification_preferences; from a public endpoint. The JSON storage shape is a database detail; the API contract should list its fields, so a future migration to a user_preferences table doesn't break consumers.

Test through the Nova endpoint, not the model. Nova's save path (mass assignment, authorize(), field resolution, validation) is where things break. A feature test posting the payload exercises the whole chain:

public function test_preferences_round_trip(): void
{
    $this->actingAs($admin)
        ->putJson("/nova-api/users/{$user->id}", [
            'notification_preferences' => ['weekly_digest' => true],
        ])
        ->assertOk();

    $this->assertTrue($user->fresh()->notification_preferences['weekly_digest']);
}

Roll out on one resource first. Pick the least important admin screen, ship it, leave it a release cycle. Promotion to permission bundles or feature flags should be a deliberate second step, not part of the initial install.

Gotchas and Tips

Silent save failures almost always mean mass assignment. The form renders, you hit save, nothing persists, no error. Check $fillable / $guarded before you suspect the package. This is the most common confusion point with custom fields.

Missing the model cast produces a raw JSON string and then an incomprehensible crash inside your array_merge. Add the cast in the same commit as the migration — never "later."

Nullable plus empty submission yields null, not an all-false object. Some servers/browsers will omit the key entirely. Your read-side normalization must tolerate null, [], and a sparse object. Test "submit with nothing checked" explicitly; it's a different code path from "check everything."

Check vendor/.../dist before blaming your own code. No prebuilt assets means an empty div with no error in the console. If your app doesn't run a Node build for vendor assets, this field will appear broken in a way that looks like a Vue mounting problem and isn't.

Nova upgrades are the sharp edge. Custom fields depend on frontend conventions that shift between majors, and this package's last release is March 2024. Pin Nova and this package together in composer.json, and re-run your CRUD/validation/authorization pass on every Nova bump. Treat Nova 5 as unverified until you've smoke-tested it.

Confirm storage parity before swapping out native BooleanGroup. If the package formats identically, rollback is a one-line change and you're safe. If it diverges (key ordering, null handling, extra wrapper keys), prove the round trip on a disposable resource and write a one-time backfill before touching production data.

Verify field-level authorization. Small packages often skip authorize(). Test with a restricted user that the field is actually read-only or absent, and confirm the rule is attached to this field rather than inherited from the resource's authorizedToUpdate.

Debug by reading the column. When the UI disagrees with reality, dd($user->getRawOriginal('notification_preferences')) and inspect the actual JSON. The field's display layer and the storage layer disagree often enough that guessing is slower than looking.

You own the regression surface. Seven stars, zero dependents, no visible test suite. Budget a couple of feature tests around persistence, validation, and authorization — that's the cheapest insurance available, and it's the thing that lets you upgrade Nova without fear.

Watch option-set growth as a design signal. Four to eight flags read well in a grouped control; twenty do not. The moment you want per-flag history, conditional display, per-tenant isolation, or metadata like "last changed by," that's a migration trigger to a real user_preferences table — not a reason to add another array entry.

Know the exit ramp and document it. MIT, small, low-adoption: assume you may need to patch or fork. Keep every direct reference confined to App\Nova\Fields\PreferenceGroup, document the JSON storage shape in the repo, and note the revert path. If parity holds, reverting to native BooleanGroup is a one-line change — and normalize-on-read is what keeps the data valid through that switch.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core