bbs-lab/nova-items-field
A Laravel Nova field for managing repeatable item lists inside your resources. Add, edit, reorder, and remove rows through a clean native Nova UI, with structured JSON storage that keeps multi-item data simple to build, validate, and maintain.
Requirements check first. This field targets Nova 5 and PHP ^8.4 with Laravel ^11 || ^12 || ^13. If your app is still on Nova 4 or PHP 8.3, you cannot adopt this release — treat it as a forward-looking option for new/upgraded projects only.
composer require bbs-lab/nova-items-field
$table->json('tags')->nullable();
// Model
protected $casts = ['tags' => 'array'];
use BBSLab\NovaItemsField\Items;
Items::make('Tags')
->chips()
->suggestions(['laravel', 'nova', 'vue'])
->rules('max:10')
->itemRules('string', 'max:255');
Where to look first: the README's method list (chips, min/max, maxHeight, inputType, suggestions, addButtonPosition, hideAddButton, indexAsChips/indexAsList, detailsAsTotal, dependsOn, copy-to-clipboard) and the migration table if you're coming from blendbyte/nova-items-field.
First use case: a tags or bullet_points JSON column on an existing resource — drop the field in, enable ->chips() for tags or leave default numbered rows for ordered content like steps, and set ->itemRules('string', 'max:255') so bad rows surface inline instead of failing at save time.
Pick the display mode by data semantics, not aesthetics.
->chips() for unordered, short, dedupable values (tags, emails, labels).Use the right validation layer.
->rules(...) = array-level rules (count/shape of the whole list).->itemRules(...) = per-element rules, rendered as inline per-row errors.->min() / ->max() = item-count constraints. Prefer these for counts over hand-written rules('max:10') — it keeps intent readable and avoids confusion with per-item max semantics.Items::make('Steps')
->min(1)
->max(20)
->rules('required', 'array')
->itemRules('required', 'string', 'max:500');
Constrain suggestions if you need strictness. ->suggestions() is a datalist (autocomplete), not a whitelist. If only known values are allowed, pair it with ->itemRules('in:laravel,nova,vue').
Tune the editing surface for the content size.
->maxHeight('320px') for long lists so the form doesn't sprawl.->inputType('email' | 'url' | ...) to get the right mobile keyboard/HTML semantics.->addButtonPosition(...) / ->hideAddButton() where the default add-button placement fights your form layout (e.g. fixed-length lists you seed elsewhere).Use the tailored index/detail views deliberately.
->indexAsChips() for short values you want visible in the table.->indexAsList() for a compact preview of ordered content.->detailsAsTotal() on detail pages when the individual values are noise and only the count matters.Wire dependencies, not custom JS. dependsOn handles the common "show this field only when a mode/flag is selected" case; keep dependent state in plain Nova fields rather than reaching for custom field components.
Copy-to-clipboard is worth enabling on detail views for values users paste elsewhere (emails, IDs, tokens-as-strings), which removes a chunk of "select the text carefully" support tickets.
Treat ordering as data. Drag-and-drop (with keyboard support) implies array order is meaningful. Do not sort the array in an accessor/mutator or saving hook — you'll silently break the reordering UX and any downstream consumers relying on position.
where('tags', 'like', ...) is a trap. Use your DB's JSON operators (whereJsonContains on MySQL/Postgres/SQLite where supported) or, if you need heavy filtering, faceting, or sorting by tag, model it as a related table instead and keep this field for lightweight lists.rules() vs itemRules() mix-ups are the most likely source of "why didn't my error show inline?" max:255 in rules() is not a per-item rule; put string-length constraints in itemRules().blendbyte/nova-items-field: the namespace changes (BBSLab\NovaItemsField\Items). Follow the README migration table method-by-method; don't assume a rename-free swap, and re-check any published views/translations you had customized.dependsOn + validation: hidden dependent fields still exist in the payload. If a field is conditionally hidden, make its rules conditional too (sometimes/required_if or ->rules() guarded by resolved state), otherwise validation can fire on values the user can't see.composer.json if you have multiple Nova apps on different tracks.[] or null and normalize on read; mixed states cause inconsistent min() behavior and confusing detail rendering.How can I help you explore Laravel packages today?