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 Tag Input Laravel Package

superlatif/nova-tag-input

A Laravel Nova field that adds a clean, interactive tag input to your resource forms. Users can easily add, remove, and manage multiple tags, making it simple to organize and categorize models directly within Nova.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Verify Nova compatibility first — it's the whole ballgame. This is a Nova custom field built against Nova's private Vue/front-end conventions, not a public contract. Open the package's composer.json and compare its nova, laravel/framework, and php constraints against your pinned versions. Nova 4 vs Nova 5 is the make-or-break axis. Floating or absent constraints on a 13-star, zero-dependent package are a red flag — test in staging before it touches a resource you care about.

2. Read the field class before the README. Spend five minutes in src/:

  • The actual FQCN of the Field subclass (the docs call it Tag — confirm the namespace before writing an import).
  • Its component() method — this is the registered Vue component name and therefore the real API surface for what the field can do.
  • Whether it ships a compiled dist/ or expects you to npm run build.
  • Whether the service provider registers publishable assets (a nova:publish step is easy to miss).

3. Install and publish.

composer require superlatif/nova-tag-input
php artisan nova:publish   # only if the provider registers publishable assets

4. Give the model a JSON column and an array cast. The cleanest storage shape — and the one this package is designed around — is a nullable JSON column:

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

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

5. Register on one resource and do a round trip. Start with the smallest possible use case: free-form keywords on a Post, Article, or Customer.

use Superlatif\NovaTagInput\Tag; // verify FQCN against src/

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Title'),
        Tag::make('Tags')
            ->placeholder('Add a keyword and press enter…')
            ->help('Free-form keywords. A handful per record.'),
    ];
}

Create a record with two or three tags, save, reload the detail view, then reopen the edit form and confirm the chips return. That one loop validates component registration, the cast, the storage format, and the read path simultaneously — the fastest viability check you can run.


Implementation Patterns

Commit to JSON-array storage and document it. The package supports arrays and delimited strings; pick the array form. It removes parsing ambiguity from validation, exports, and reporting, and keeps the data queryable. Treat the persisted shape as an internal contract and write the shape in a comment next to the field registration — future-you will not remember whether it's ["a","b"] or "a,b".

Normalize at the model boundary, not in the form. Every write path — Nova, seeders, imports, tinker, artisan commands — should produce identical output. A mutator handles trimming, de-duplication, and empty-state collapse in one place:

public function setTagsAttribute($value): void
{
    $tags = collect(is_array($value) ? $value : explode(',', (string) $value))
        ->map(fn ($t) => trim((string) $t))
        ->filter()
        ->unique()
        ->values()
        ->all();

    $this->attributes['tags'] = $tags === [] ? null : json_encode($tags);
}

Enforce limits in validation, not in the UI. A chip input has no natural ceiling — a user can paste forty tags. Cap total count, cap per-tag length, and reject empties after trimming. Dotted rules through Nova can be version-sensitive, so verify which form actually binds:

Tag::make('Tags')
    ->rules(['array', 'max:10'])
    ->rules(fn () => ['tags.*' => ['string', 'max:40']]);

If dotted rules don't land reliably, move the constraint into a FormRequest or a model-level guard. Either way, those three rules should exist on day one.

Reuse it through a factory, not copy-paste. When two or three resources need tagging, centralize the chain so there's one place to change when (not if) the package needs patching or replacing:

class Fields
{
    public static function tags(string $name = 'Tags'): Tag
    {
        return Tag::make($name)
            ->placeholder('Add a tag…')
            ->rules(['array', 'max:10'])
            ->help('Max 10 tags, 40 characters each.');
    }
}

Render it sensibly on index and detail. Array attributes can show as JSON noise in the index table. Flatten for display or hide it:

Tag::make('Tags')
    ->displayUsing(fn ($value) => collect($value ?? [])->implode(', '))
    ->hideFromIndex(); // if chips don't belong in the table

Treat the field as input-only; write queries separately. A JSON column is not text — LIKE will be wrong or slow. Use whereJsonContains on MySQL/Postgres. If tag filtering becomes a first-class admin need, plan a generated column or a proper taggables join table; the field is a widget, not a query layer.

Migrating from a comma-separated column? Don't repoint the field at the old text column. Add the JSON column, backfill with a one-off command that splits, trims, de-dupes, and re-encodes, verify against a production-like snapshot, then cut the field over. Keep the old column until the backfill is confirmed, and dual-read for a release cycle if you're replacing another tag package.

Test the contract, not the component. You can't meaningfully unit-test the Vue side from PHP. Cover what can corrupt data: a feature test that posts a tag array to the Nova resource endpoint and asserts the persisted JSON; a mutator test for duplicates and whitespace; a validation test that eleven tags fails.

Pilot behind a flag. Ship it on one resource in one admin area and keep the previous representation available for a release. With 13 stars and zero dependents, you are almost certainly the largest consumer of this package — no ecosystem will absorb a regression for you.


Gotchas and Tips

Nova majors break custom fields — assume it. This is the dominant risk. A Nova upgrade can make the component throw or silently disappear, with no upstream fix if the maintainer has moved on. Budget one to two days a year for upgrade revalidation, test on the exact staging Nova version rather than "latest," and keep a plain textarea behind a one-line switch so an upgrade morning isn't a crisis.

Pin the version. Floating constraints on a single-maintainer package are how you get a silent storage-format change. Lock the exact version in composer.lock and add it to your dependency-review list.

Empty states are three different things. null, [], and "" may each be treated differently by the field, the cast, and your validation. Normalize to null (or [] — just pick one) in the model and assert it in a test. Most "why is there one blank chip" bugs come from this ambiguity.

Field renders blank? Work through it in order: (1) did nova:publish run, and is the compiled asset actually under public/vendor/? (2) does component() in the field class match a registered Vue component? (3) open the browser console — a Nova/Vue mismatch surfaces as a Vue warning or failed import, not a PHP exception, so storage/logs will show nothing. (4) does the package ship dist/, or does it expect a local npm run build?

Don't promise autocomplete. There is no tag library, no suggestion engine, and no create-new-tag workflow. If a stakeholder sees chips and assumes typeahead against your existing tag list, correct that before the demo — it's a different and much larger piece of work.

Long tags and special characters. A 200-character tag wrecks the chip layout and probably the column. Cap length in validation. A low-adoption package is exactly where you verify rather than assume escaping — test with <script>, quotes, commas, emoji, and RTL text before shipping.

Authorization and read-only states. Confirm the field honors canSee / canSeeWhen / readonly like native Nova fields. Custom components occasionally stay editable when the resource is locked down. Verify with a non-admin user before this reaches a role-scoped admin area; a read-only viewer rewriting tags is a real, if small, access-control bug.

Name your exit path now. Since the field is a thin wrapper over a Vue component, you have three escalating escape hatches: subclass the field and override component() to point at your own Vue component; fork the package (it's small — a few hundred lines); or replace it with an in-house Nova field. Decide which trigger takes you where, and write it in the same doc as the storage format. Naming the exit is what keeps this a pragmatic dependency rather than a future incident.

Watch payload size. Backend cost is one column read/write, so scaling is negligible — until someone pastes 300 tags into one record and the edit form gets sluggish. The max:10 rule isn't just hygiene; it's a performance guardrail.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
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