whchi/nova-tags-input
A Laravel Nova field that adds a tag-style input to your resources, letting users type, select, and create multiple tags in one place. Configurable suggestions and simple storage make it easy to attach array-based tags to any model.
This is a thin Nova field wrapper around JohMun's vue-tags-input. It gives you a tag-entry UI in Nova forms and stores the value as a JSON array of { text: "..." } objects. Before you install anything, note the metadata: archived, 2 stars, 0 dependents. Treat this as a reference implementation or short-term stopgap, not a long-term dependency.
First steps:
Verify Nova compatibility before writing code. Check the package's composer.json (laravel/nova constraint) and its package.json (vue-tags-input version). If the field is pinned to [email protected], it targets Vue 2 and therefore Nova 3 and earlier. Nova 4/5 run Vue 3 and need [email protected]. This one check will save you an afternoon.
Install into an existing Nova app:
composer require whchi/nova-tags-input
php artisan nova:publish # if the package ships assets you need to publish
Confirm the compiled field JS actually landed in public/vendor/... — archived Nova packages frequently ship stale dist/ bundles.
Look first at vendor/whchi/nova-tags-input/resources/js/components/FormField.vue and field.js. That is the whole package: a FormField mixin, a DetailField, and a vue-tags-input binding. Reading ~150 lines tells you everything about the data shape and available meta options.
First use case: tagging a Post.
// app/Nova/Post.php
use Whchi\NovaTagsInput\Tags; // confirm exact class/namespace in the vendor dir
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title'),
Tags::make('Tags'),
];
}
// migration
$table->json('tags')->nullable();
// app/Models/Post.php
protected $casts = ['tags' => 'array'];
If the field renders but the value round-trips as null, the problem is almost always the cast or a prepareForValidation mismatch — see Gotchas.
Store as JSON, not CSV. vue-tags-input emits an array of objects, so a json column plus an array cast is the only shape that round-trips without a custom accessor. If you inherit a comma-separated column, normalize in the model rather than patching the field:
public function setTagsAttribute($value)
{
$this->attributes['tags'] = json_encode(
collect($value)->map(fn ($t) => is_array($t) ? $t['text'] : $t)->values()->all()
);
}
Wrap it in a factory method so you can change the underlying implementation later without touching every resource:
// app/Nova/Fields/TagField.php
public static function make(string $name = 'Tags')
{
return Tags::make($name)->help('Press Enter or comma to add a tag.');
}
Autocomplete from existing tags. If the field exposes a suggestions meta key (check FormField.vue for this.field.* reads), populate it from the DB in fields():
Tags::make('Tags')
->withMeta(['suggestions' => Post::query()
->pluck('tags')->flatten(1)->pluck('text')->unique()->values()])
If no such hook exists upstream, that's your first local patch — a 3-line computed property in FormField.vue plus a withMeta call. Because the package is archived, patching the vendor copy (with a fork or a path repository) is a legitimate pattern here.
Query by tag with MySQL/Postgres JSON operators:
Post::whereJsonContains('tags', [['text' => 'laravel']])->get();
Detail and index views need different formatting than the form. Override resolve/displayUsing or add a small accessor so the index shows laravel, nova, php instead of [{"text":"laravel"}]:
public function resolve($resource, $attribute = null)
{
parent::resolve($resource, $attribute);
$this->value = collect($resource->{$this->attribute} ?? [])
->pluck('text')->implode(', ');
}
Pair it with server-side validation on the resource or form request — the field's own validation is client-only:
'tags' => ['array'],
'tags.*.text' => ['string', 'max:32'],
Consider the built-in first. Nova ships Laravel\Nova\Fields\Tags (since Nova 2). If you only need free-text tags with index/detail rendering, use it. Reach for nova-tags-input only when you specifically want vue-tags-input's UX: inline validation styling, delimiter control, autocomplete dropdown, and tag-level ti-* CSS hooks for theming.
composer.json without forking it. composer require will work, but there will be no security patches, no Nova 5 fixes, and no Vue 3 migration. Score it as "read once, fork if needed."[email protected] does not. If the bundled field fails with a blank form area and no console error beyond a Vue warn, check the browser console for Cannot read properties of undefined (reading 'extend') or similar — that's a Vue 2 component inside a Vue 3 host.vue-tags-input wants [{ text: 'foo' }]; almost every other Laravel tagging solution (Spatie's HasTags, Nova's native Tags) uses ['foo', 'bar']. Decide on one canonical shape at the DB layer and convert at the boundary. Half the bugs with this package are shape-conversion bugs.resources/js/ does nothing until you run npm run dev in the package directory and copy dist/ back. Most archived Nova packages ship a dist/ that no longer matches resources/.null vs []. An empty tag list comes back as [] but Nova's FormField default may submit null. Add $table->json('tags')->nullable() and a ?? [] in your accessor, or whereJsonContains on an empty column will silently return nothing.->sortable() a JSON tag column meaningfully. If filtering/sorting by tag is a requirement, model tags as a proper pivot table and use a BelongsToMany field for selection, with this field only as a free-form overlay.php artisan nova:field in Nova 4+ scaffolds a modern Vue 3 field in ~30 seconds. When this package breaks, replacing it with a generated field that imports vue-tags-input@2 is usually faster than diagnosing it — and that's the honest recommendation given the maintenance signal.How can I help you explore Laravel packages today?