Custom Uniqueness Validation with Spatie Tags in Laravel
James Manager
The Spatie Tags package is a go-to solution for adding tag support to your Laravel models. One standout feature is its built-in support for multilingual tags, storing name and slug as JSON fields like:
{
"en": "Manufacturer",
"fr": "Fabricant"
}But this multilingual power also introduces a challenge: how do you validate that a tag name doesn't already exist—especially before it's saved? In this post, we’ll create a custom Laravel validation rule that:
- Works with Spatie’s JSON-based name and slug
- Checks uniqueness using whereJsonContains
- Can be scoped to a tag type like "category" or "label"
By default, Spatie Tags prevents identical tag slugs in the same language. However:
- It doesn’t validate at the form level
- It won’t warn users before submission
- You may want to customize which tag types are unique
If you're using Spatie Tags and building admin forms with Filament, this guide shows how to create a custom Laravel validation rule for tags stored in JSON fields across multiple languages. First thing to do is to create a Custom Validation Class
<?php
namespace App\Rules;
use App\Models\Tag;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class UniqueNameTag implements ValidationRule
{
protected $ignoreId;
public function __construct($ignoreId = null)
{
$this->ignoreId = $ignoreId;
}
/**
* Run the validation rule.
*
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$query = Tag::query()->where('type', Tag::CATEGORY);
if ($this->ignoreId) {
$query->where('id', '!=', $this->ignoreId);
}
$exists = $query->whereRaw('LOWER(json_unquote(json_extract(`name`, "$.' . app()->getLocale() . '"))) = ?', [strtolower($value)])
->exists();
if ($exists) {
$fail("A tag with the name '{$value}' already exists.");
}
}
}Then you use the rule in your Form. Since I'm using Filament, this is how it would be
TextInput::make('name')
->label('Tag Name')
->placeholder('Enter tag name...')
->required()
->rules([
new UniqueNameTag(),
])Also when edit, you can pass the same unique validation rule
TextInput::make('name')
->label('Tag Name')
->rules([
fn(Tag $record) => new UniqueNameTag($record->id),
])
Share this article
Related Articles
Adding a Live Chat Widget to Your Filament Dashboard
Discover how to add a live chat widget to your Filament app and pass user details for personalized support.
How to Implement Conditional Sub-Navigation in Filament v4 Resources
Master targeted page navigation in Filament by controlling when and where sub-navigation tabs appear in your resource pages.
15+ Top Open Source Filament PHP Projects
Learn from the best with our curated list of open-source Filament PHP projects, perfect for customization or inspiration