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 Key Value Laravel Package

hnassr/nova-key-value

A Laravel Nova field for editing key-value pairs directly in the admin panel. Store flexible JSON or array data with a clean, repeatable interface, making it easy to manage settings, metadata, and custom options without building a custom form.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is an archived Nova 2-era field that renders an editable key/value grid and persists the result as JSON on a single model attribute. Treat it as a vendored dependency rather than a maintained one: read composer.json and src/KeyValue.php before installing, because the version constraints will not resolve against Nova 4/5.

1. Confirm compatibility before you type composer require

# in the package repo (or vendor dir after install)
cat composer.json        # expect laravel/nova ^2.0 and php ^7.1
cat package.json         # vue ^2.5 — a Nova 4 (Vue 3) app cannot use this

If you're on Nova 4+, stop here and either fork the repo (the Vue component is ~100 lines) or use spatie/laravel-data + a plain Text field. The rest of this assessment assumes Nova 2/3 and PHP 7.x.

2. Install and register

composer require hnassr/nova-key-value

Nova 2 auto-discovers service providers; if it doesn't, add Hnassr\NovaKeyValue\FieldServiceProvider::class to config/app.php. No publish step is required — the compiled dist/js/field.js ships in the repo.

3. Add the column and the cast (both are mandatory)

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

// app/Models/Product.php
protected $casts = [
    'settings' => 'array',   // 'json' also works; 'array' is safer for round-trips
];

Without the cast, Nova will hand the component a raw string and the write path will either double-encode the JSON or throw Array to string conversion.

4. Attach the field

use Hnassr\NovaKeyValue\KeyValue;

public function fields(Request $request)
{
    return [
        KeyValue::make('Settings')
            ->rules('array')
            ->hideFromIndex(),
    ];
}

First use case: a single settings JSON column on a resource you edit rarely — payment gateway options, per-tenant feature flags, product shipping attributes. It is a form editor for a JSON blob, not a relational structure; if you need querying, sorting, or per-key validation, model the keys as real columns instead.

Where to look first: src/KeyValue.php for the fluent API actually exposed (labels, placeholders, default value), resources/js/components/FormField.vue for how the array is serialized on save, and dist/js/field.js to confirm the compiled asset matches the source (it often doesn't in abandoned packages).

Implementation Patterns

Basic resource field with sensible display split

KeyValue::make('Settings')
    ->rules('array')
    ->hideFromIndex()                       // raw JSON in a table cell is unreadable
    ->displayUsing(function ($value) {      // detail view
        return collect($value ?? [])
            ->map(fn ($v, $k) => "{$k}: {$v}")
            ->implode(' · ');
    }),

Seeding defaults for new records

KeyValue::make('Settings')
    ->default([
        'currency' => 'USD',
        'timezone' => 'UTC',
    ])
    ->resolveUsing(fn ($value) => $value ?? ['currency' => 'USD'])

default() only applies on the create form; if you also want the model to be safe when created outside Nova, back it with a column default or a model booted() hook.

Per-key validation when the field is the source of truth

The field does not validate individual keys — ->rules('array') is the ceiling. Enforce shape in a FormRequest or a custom rule:

->rules(['array:currency,timezone', 'currency.string', 'timezone.timezone'])
// or, more flexibly:
->rules(['array', new ValidSettingsShape()])

array:key1,key2 also doubles as an allow-list, which prevents users from inventing junk keys the rest of your app doesn't understand.

Reading the value in application code

Because everything is a string, normalize at the boundary rather than scattering casts:

class Settings
{
    public function __construct(private array $raw) {}

    public function bool(string $key): bool
    {
        return filter_var($this->raw[$key] ?? false, FILTER_VALIDATE_BOOLEAN);
    }
}

Bind this as a cast or an accessor (getSettingsAttribute) so controllers and Blade never touch the raw array.

Store on a different attribute than the label

If the human label differs from the column, use ->resolveUsing() / ->fillUsing() pairs rather than renaming the column:

KeyValue::make('Payment Options', 'payment_options')

Single-resource "settings page" pattern

For app-wide config, create a Setting model with one row and a Settings Nova resource whose fields() return a handful of KeyValue fields grouped by concern (billing, mail, integrations). Guard the resource with a policy so only admins see it, and override authorizable() / hide the "create" button via canSee().

Gotchas and Tips

The package is abandoned — plan for ownership. Last release June 2019, targeting Nova 2. There is no Nova 4 compatibility, no Vue 3 build, and no security response. If it's load-bearing, copy resources/js/components/FormField.vue into your own nova-components/ package on day one rather than discovering the problem during a Nova upgrade.

Compiled assets may not match the source. Abandoned packages frequently ship a dist/js/field.js older than the .vue file. If a documented option has no effect, cd vendor/hnassr/nova-key-value && npm install && npm run prod inside the package, or fork and build.

Keys are reordered when they look numeric. JSON objects round-trip through JavaScript objects, and integer-like keys ("1", "2") are enumerated first. Use string keys ("opt_1") and never rely on insertion order for display.

Duplicate keys silently collapse. The UI will happily let a user add the same key twice; the JSON encoder keeps the last one. Add a uniqueness check in your FormRequest — the component does not do it.

Everything is a string. "false", "0", "null" and "" all survive as strings. Cast explicitly where consumed, and decide early whether empty values mean "unset" or "empty string" — the field's own empty-state handling differs between create and update.

Empty vs. null. Saving a form with an empty grid typically persists [], not null. If downstream code branches on is_null($model->settings), either make the column nullable() with a ->default(null) or normalize in a mutator.

Nested keys are not supported. meta->theme->color won't work as a field name; the attribute is a flat map. Model hierarchies as separate columns or a dedicated JSON resource.

Validation is shallow by design. ->rules('array') accepts any array. Anything that reads a key in production needs a shape rule, or a missing key becomes a null-reference bug rather than a form error.

Sensitive values leak into Nova payloads. Gateway keys and tokens stored here appear in the index, detail, and the raw JSON response. Combination that works: ->hideFromIndex(), ->hideFromDetail(), an encrypted:array cast on the model — but note the field then shows ciphertext in the form, so you'll need a custom resolveUsing that decrypts for editing and re-encrypts on fill. For true secrets, use laravel/nova's password field or a separate vault, not this.

Debugging checklist, in order: (1) is the column actually JSON and cast? (2) does the browser network response show an object or a string? (3) does dist/js/field.js exist and load — check the Network tab for a 404 on nova-api/scripts? (4) php artisan nova:publish isn't needed here, but php artisan view:clear and a hard browser refresh resolve most stale-asset ghosts.

Extension points. fillUsing() and resolveUsing() let you decouple the field name from the storage attribute, merge defaults, or write to a related model. Subclassing KeyValue is the cleanest way to bake in your rules and display format, and lets you swap out the underlying component in one place if you later fork.

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.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics