In this article, we will learn how to achieve composite unique key validation in filament.
In most practical scenarios, a single column is insufficient to ensure uniqueness.
For example:
- Roll number unique per class or per section
- section name should be unique per grade
- subject code unique per semester and so on..
This is known as composite unique constraint, where more than one column need to be unique together. In this article, we will learn how to achieve composite unique key validation in filament.
Single Column Unique Validation
In Filament, using a unique validation rule on a single column is very easy. You just chain the unique() method directly on the form component to validate uniqueness on the column level
TextInput::make('code')
->label(__('resource.career.fields.code'))
->required()
->unique(),
In the above code, ->unique auto handles the validation.
Look at other validation rules.
Composite Unique Key
Lets assume, we have the following migration:
Schema::create('sections', function (Blueprint $table) {
$table->id();
$table->foreignId('grade_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->timestamps();
$table->unique(['grade_id', 'name']);
});
The above migration ensures that the grade_id and name are always unique as the database level.
Filament Form Component (Create Mode)
Now, in our filament resource:
TextInput::make('name')
->required()
->rules([
fn ($get) => Rule::unique('sections')
->where('grade_id', $get('grade_id'))
]);
Here,
Rule::unique(‘sections’) -> check uniqueness in sections table,
->where(‘grade_id’, $get(‘grade_id’)) -> add second condition
so, it validates combination of grade_id + name.
Edit Mode
Most importantly, in edit we must need to ignore the current record, else the update will always fail.
TextInput::make('name')
->required()
->rules([
fn ($get, $record) => Rule::unique('sections')
->where('grade_id', $get('grade_id'))
->ignore($record?->id)
]);
Here ->ignore($record?->id) ignores the current model during update and prevents false validation errors.
Other ways
There are many other alternative ways for composite unique key validation in filament.
1. beforeCreate or beforeSave hooks in resource
You can use lifecycle hooks method to add the validation logic too.
protected function beforeCreate(): void
{
$exists = Sections::where('grade_id', $this->data['grade_id'])
->where('name', $this->data['name'])
->exists();
if ($exists) {
Notification::make()
->title('This combination already exists.')
->danger()
->send();
$this->halt();
}
}
Create a custom rule
You can also create a custom rule in laravel and use that for validation. Please check this article to implement the custom rule.
You can also check out this video.
Create a model level observer
You can also create an observer and use the same validation logic. This works for seeders, API but gives less control over API. You need to throw and catch an exception.
Create a custom validator
You can also create the custom validator and register in your provider.
Validator::extend('unique_combination', function ($attribute, $value, $parameters, $validator) {
$data = $validator->getData();
return !DB::table($parameters[0])
->where($parameters[1], $value)
->where($parameters[2], $data[$parameters[2]])
->exists();
});
Then we can use that in our field:
->rules(['unique_combination:sections,grade_id,name'])
Conclusion
| Approach | Reusbale | UI Control | Works outside filament |
| Rule::unique() | No | Field level error | No |
| Custom Validator | Partial | Field Level Error | Yes |
| beforeCreate/Save lifecycle hook | No | Notification | NO |
| Custom Rule Class | Yes | Field-level error | Yes |
| Model Observer | Yes | Limited | Yes |
For most Filament use cases, combining Custom Rule Class (for reusability) + database unique constraint (as a safety net) is the recommended approach.
In this way, we can achieve composite unique key validation in filament.

