spatie/nova-tags-field
Nova field for tagging resources using spatie/laravel-tags. Add the HasTags trait to your Eloquent models and use the Tags field in Nova to create, assign, and manage tags. Requires MySQL 5.7.8+ and installs via Composer.
Installation
composer require spatie/nova-tags-field
Then publish the migration for the underlying spatie/laravel-tags package:
php artisan vendor:publish --provider="Spatie\Tags\TagsServiceProvider" --tag="migrations"
php artisan migrate
Register the Field Add the field to your Nova resource:
use Spatie\NovaTagsField\TagsField;
class Post extends Resource
{
public function fields(Request $request)
{
return [
// ...
TagsField::make('Tags'),
];
}
}
First Use Case
tags pivot table (created by spatie/laravel-tags).Basic Tagging
Use the default TagsField for simple tagging:
TagsField::make('Tags')->onlyOnDetail(true); // Optional: Show only on detail view
Customizing Display Override how tags are displayed in lists or details:
TagsField::make('Tags')
->displayUsing(function ($tags, $resource) {
return $tags->pluck('name')->implode(', ');
});
Validation & Rules Validate tags in your resource model:
use Spatie\Tags\HasTags;
class Post extends Model
{
use HasTags;
protected $rules = [
'tags' => 'required|min:1', // Example: Ensure at least 1 tag
];
}
Integration with Search
Use the underlying spatie/laravel-tags package to search by tags:
$posts = Post::withTags($request->input('tag'))->get();
Bulk Actions Add a bulk action to filter resources by tags:
public function availableForIndex(Request $request)
{
return $request->user()->can('view posts') ? Post::withTags($request->input('tag')) : Post::none();
}
Tag Suggestions Preload common tags for autocomplete:
TagsField::make('Tags')
->suggestedTags(['laravel', 'nova', 'php']);
Migration Conflicts
spatie/laravel-tags migration runs before your resource migrations to avoid table/column conflicts.tagsTable() method in your model:
class Post extends Model
{
use HasTags;
public function tagsTable()
{
return 'post_tags'; // Custom pivot table
}
}
Tag Syncing
TagsField uses sync() by default. If you need attach()/detach(), override the field’s resolveAttribute:
TagsField::make('Tags')
->resolveUsing(function ($resource) {
return $resource->tags()->pluck('name');
})
->storeUsing(function ($resource, $tags) {
$resource->tags()->sync($tags);
});
Performance with Large Datasets
withCount('tags') instead:
$posts = Post::withCount('tags')->get();
Case Sensitivity
protected static function bootHasTags()
{
static::saving(function ($model) {
$model->tags = collect($model->tags)->map(fn($tag) => strtolower($tag));
});
}
Nova Tool Conflicts
spatie/nova-search), ensure they don’t conflict with tag queries. Use scopes or custom queries to isolate logic.Check Pivot Table Verify tags are saved correctly:
php artisan tinker
>>> \App\Models\Post::first()->tags
Log Field Resolution Debug field resolution by adding a temporary log:
TagsField::make('Tags')
->resolveUsing(function ($resource) {
\Log::debug('Resolving tags for post ID: ' . $resource->id);
return $resource->tags()->pluck('name');
});
Clear Cached Views If tags don’t update in Nova, clear the view cache:
php artisan view:clear
Custom Tag Model
Extend the default Tag model:
class CustomTag extends \Spatie\Tags\Tag
{
public function scopeActive($query)
{
return $query->where('is_active', true);
}
}
Then bind it in AppServiceProvider:
public function boot()
{
\Spatie\Tags\Tag::swap(new \App\Models\CustomTag());
}
Custom Tag Field UI Override the Vue component for advanced UI (e.g., color-coded tags):
Nova.require('@spatie/nova-tags-field', (resolve) => {
return resolve.then(() => {
Nova.booting((Vue, router, store) => {
router.addRoutes({
name: 'custom-tags-field',
path: '/custom-tags-field',
component: require('./CustomTagsField.vue'),
});
});
});
});
Tag Metadata Store additional metadata (e.g., tag color) in the pivot table:
Schema::create('post_tags', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->string('color')->nullable(); // Custom field
$table->timestamps();
});
How can I help you explore Laravel packages today?