💡 Custom Regulatory Compliance Dashboard See Demo

Custom Uniqueness Validation with Spatie Tags in Laravel

| 5 min read
James Manager

James Manager

Custom Uniqueness Validation with Spatie Tags in Laravel

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),
    ])